fix(views): the view bar counted rows the table was not showing

Every page built its own "N of M" from its OWN array, which is only
search-filtered -- the column rules are applied inside DataTable. So the bar
read "4 of 4" while the table showed 2, on every page, the moment any rule was
active. Moving filtering into the columns is what introduced it.

useTableChrome now exposes `shownRows`/`shownCount`, computed with the same
engine the table uses, so the two cannot disagree. Fifteen call sites moved
onto it, plus the DataTableFooter counts that told the same lie.

ResourceListPage had a worse version: it sliced `items` for the page size and
DataTable filtered afterwards, so a page size of 25 took 25 UNFILTERED rows and
then filtered them -- "show 25" could render three. It pages `chrome.shownRows`
now, so the slice and the render agree.

The Security tabs already computed this correctly by calling applyFilters
themselves; they are left alone rather than churned.

Guarded going forward: the preset prober now reads `.sk-viewbar__count` after
selecting each view and fails on !BARCOUNT if the number disagrees with the
rendered rows. 0 mismatches across 11 targets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Juan DenisandClaude Opus 5 committed 2026-08-12 17:38:58 -04:00
1 parent 086073d7e5
commit 002fb1bccb
16 files changed
+41 -25

No files matched your search

@@ -1,6 +1,6 @@
import { useCallback, useMemo, useState } from 'react';
import useGridViews from './useGridViews';
import { OPS, emptyValueFor, isFilterable, ruleId, withInferredTypes } from './fields';
import { OPS, applyFilters, emptyValueFor, isFilterable, ruleId, withInferredTypes } from './fields';
const NO_FILTERS = { match: 'all', rules: [] };
@@ -149,12 +149,24 @@ export function useTableChrome({
resetToView: api.resetToView,
});
// What the table will ACTUALLY render. `rows` is the page's own already
// searched/filtered array; the column rules are applied by <DataTable>
// itself, so a page counting `rows.length` for its view bar reported "4 of
// 4" while the table showed 2. Computed here, once, from the same engine
// the table uses, so the two can never disagree.
const shownRows = useMemo(
() => applyFilters(rows, filters, orderedColumns),
[rows, filters, orderedColumns],
);
return {
cfg,
api,
views,
columns: orderedColumns,
noun,
shownRows,
shownCount: shownRows.length,
filterCount: filters.rules.length,
drawerOpen,
setDrawerOpen,
@@ -173,11 +173,6 @@ export default function ResourceListPage({
}
};
const pagedItems = useMemo(
() => (pageSize === 'all' ? items : items.slice(0, pageSize)),
[items, pageSize],
);
// The chrome — view picker, chip bar, filter drawer, tools menu, shareable
// links — comes from the shared hook rather than being adapted again here.
// This wrapper used to carry its own copy of that adapter, which is how it
@@ -218,6 +213,15 @@ export default function ResourceListPage({
const orderedColumns = chrome.columns;
const tableViews = chrome.views;
// Paged AFTER the column rules, not before. Slicing `items` meant a page
// size of 25 took 25 unfiltered rows and THEN filtered them, so "show 25"
// could render three. `chrome.shownRows` is the same set the table renders,
// so the two agree; DataTable re-applying the rules to it is a no-op.
const pagedItems = useMemo(
() => (pageSize === 'all' ? chrome.shownRows : chrome.shownRows.slice(0, pageSize)),
[chrome.shownRows, pageSize],
);
if (loading) {
// Same wrapper as the loaded state below. A skeleton that renders
// outside the page's padded container occupies a different box than the
@@ -260,7 +264,7 @@ export default function ResourceListPage({
<GridViewPicker
views={tableViews}
label={noun}
total={`${items.length} of ${resolvedTotal} ${noun}`}
total={`${chrome.shownCount} of ${resolvedTotal} ${noun}`}
onCreate={chrome.createView}
/>
)}
@@ -365,7 +369,7 @@ export default function ResourceListPage({
footer={(
<DataTableFooter
shown={pagedItems.length}
total={items.length}
total={chrome.shownCount}
noun={noun.replace(/s$/, '')}
pageSize={pageSize}
onPageSizeChange={changePageSize}
@@ -293,7 +293,7 @@ const ApiKeysSection = () => {
<GridViewPicker
views={chrome.views}
label="keys"
total={loading ? null : `${keys.length} key${keys.length === 1 ? '' : 's'}`}
total={loading ? null : `${chrome.shownCount} of ${keys.length} key${keys.length === 1 ? '' : 's'}`}
onCreate={chrome.createView}
/>
<ListToolbar
@@ -277,7 +277,7 @@ const InvitationsTab = () => {
<GridViewPicker
views={chrome.views}
label="invitations"
total={loading ? null : `${invitations.length} invitation${invitations.length === 1 ? '' : 's'}`}
total={loading ? null : `${chrome.shownCount} of ${invitations.length} invitation${invitations.length === 1 ? '' : 's'}`}
onCreate={chrome.createView}
/>
<ListToolbar
@@ -309,7 +309,7 @@ const MigrationHistoryTab = () => {
<GridViewPicker
views={chrome.views}
label="migrations"
total={`${revisions.length} revision${revisions.length === 1 ? '' : 's'}`}
total={`${chrome.shownCount} of ${revisions.length} revision${revisions.length === 1 ? '' : 's'}`}
onCreate={chrome.createView}
/>
<ListToolbar
@@ -360,7 +360,7 @@ const UsersTab = () => {
<GridViewPicker
views={chrome.views}
label="users"
total={`${users.length} user${users.length === 1 ? '' : 's'}`}
total={`${chrome.shownCount} of ${users.length} user${users.length === 1 ? '' : 's'}`}
onCreate={chrome.createView}
/>
<ListToolbar
+2 -2
View File
@@ -467,7 +467,7 @@ const CronJobs = () => {
<GridViewPicker
views={chrome.views}
label="jobs"
total={`${shown.length} of ${jobs.length} jobs`}
total={`${chrome.shownCount} of ${jobs.length} jobs`}
onCreate={chrome.createView}
/>
<ListToolbar
@@ -503,7 +503,7 @@ const CronJobs = () => {
rowClassName={(job) => (job.enabled ? undefined : 'is-disabled')}
footer={(
<DataTableFooter
shown={shown.length}
shown={chrome.shownCount}
total={jobs.length}
noun="job"
/>
+1 -1
View File
@@ -268,7 +268,7 @@ export default function DeliveryLog() {
<GridViewPicker
views={chrome.views}
label="deliveries"
total={`${deliveries.length} of ${stats?.total ?? deliveries.length} deliveries`}
total={`${chrome.shownCount} of ${stats?.total ?? deliveries.length} deliveries`}
onCreate={chrome.createView}
/>
<ListToolbar
+1 -1
View File
@@ -391,7 +391,7 @@ const FleetProxy = () => {
<GridViewPicker
views={chrome.views}
label="servers"
total={loading ? null : `${rows.length} server${rows.length === 1 ? '' : 's'}`}
total={loading ? null : `${chrome.shownCount} of ${rows.length} server${rows.length === 1 ? '' : 's'}`}
onCreate={chrome.createView}
/>
<ListToolbar
+1 -1
View File
@@ -395,7 +395,7 @@ export default function Incidents() {
<GridViewPicker
views={chrome.views}
label="incidents"
total={`${shown.length} of ${items.length} · ${openIncidents} open · ${activeAlerts.length} host alerts firing`}
total={`${chrome.shownCount} of ${items.length} · ${openIncidents} open · ${activeAlerts.length} host alerts firing`}
onCreate={chrome.createView}
actions={(
<>
+2 -2
View File
@@ -489,8 +489,8 @@ export default function Jobs() {
views={chrome.views}
label="jobs"
total={hasFilters
? `${jobs.length} of ${total} matching · ${allJobs} jobs total`
: `${jobs.length} of ${allJobs} jobs`}
? `${chrome.shownCount} of ${total} matching · ${allJobs} jobs total`
: `${chrome.shownCount} of ${allJobs} jobs`}
onCreate={chrome.createView}
/>
<ListToolbar
+1 -1
View File
@@ -461,7 +461,7 @@ export default function Monitors() {
label="monitors"
total={(
<>
{monitors.length} monitor{monitors.length === 1 ? '' : 's'}
{chrome.shownCount} of {monitors.length} monitor{monitors.length === 1 ? '' : 's'}
{stats?.overall_uptime_30d != null && (
<> &middot; {stats.overall_uptime_30d}% uptime (30d)</>
)}
+1 -1
View File
@@ -389,7 +389,7 @@ const QueueDetail = () => {
<GridViewPicker
views={chrome.views}
label="messages"
total={`${messages.length} loaded`}
total={`${chrome.shownCount} of ${messages.length} loaded`}
onCreate={chrome.createView}
/>
+1 -1
View File
@@ -565,7 +565,7 @@ const QueueOperations = () => {
<GridViewPicker
views={chrome.views}
label="queues"
total={`${filteredQueues.length} of ${queues.length} queues`}
total={`${chrome.shownCount} of ${queues.length} queues`}
onCreate={chrome.createView}
/>
+2 -2
View File
@@ -371,7 +371,7 @@ const Servers = () => {
<GridViewPicker
views={chrome.views}
label="servers"
total={`${filteredServers.length} of ${servers.length} servers`}
total={`${chrome.shownCount} of ${servers.length} servers`}
onCreate={chrome.createView}
/>
<ListToolbar
@@ -446,7 +446,7 @@ const Servers = () => {
tableClassName="servers-table"
footer={(
<DataTableFooter
shown={filteredServers.length}
shown={chrome.shownCount}
total={servers.length}
noun="server"
/>
+1 -1
View File
@@ -377,7 +377,7 @@ export default function Telemetry() {
<GridViewPicker
views={chrome.views}
label="events"
total={`${events.length} loaded · ${stats?.total || 0} in the last 24h`}
total={`${chrome.shownCount} of ${events.length} loaded · ${stats?.total || 0} in the last 24h`}
onCreate={chrome.createView}
// The "..." sits on the view-name line rather than in a toolbar
// of its own -- with nothing else to put there, that toolbar was