Repository navigation
UI Consistency: 1 issue found
apps/web/src/components/usage/UsagePage.tsx — the visible provider list is now derived from reported usage sources, but UsageSkeleton still renders one row per PROVIDER_ORDER. When a provider is disabled, the placeholder reserves two provider rows while the loaded page renders one, so the summary column and the content below it shift when the last environment answers, breaking the skeleton's documented "loaded page's shape" contract. Suggested fix: pass the derived provider list into the skeleton, falling back to PROVIDER_ORDER only while nothing has been reported.
No other consistency problems found: the chart, breakdown header/body and colSpan={providers.length + 3} stay in sync, the new providers prop is threaded through buildPeriodColumns/buildDayColumns consistently, and no shared primitives, Tailwind ownership, theme variants, or scroll/mask behavior were changed.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Files examined in scope: apps/web/src/components/usage/UsagePage.tsx, apps/web/src/components/usage/UsageProviderChart.tsx, apps/web/src/components/usage/UsageProviderChart.test.ts, apps/web/src/components/usage/usageProviders.ts, apps/web/src/state/usage.ts. Context read outside scope: apps/server/src/usage/UsageService.ts, packages/shared/src/usageMerge.ts, packages/shared/src/serverSettings.ts.
Checks performed:
- Verified table column arithmetic: header and body both emit
1 + providers.length + 2cells, matching the new empty-statecolSpan. - Verified band indexing stays aligned between
buildPeriodColumnsand theproviders.mappath builder, and thatproviderswas added to the chart memo dependency list. - Confirmed no raw controls, class-string overrides of primitives, global CSS, theme variant, or scroll/mask behavior were touched by the diff.
- Confirmed the server emits a source entry per enabled provider directory even when it is missing, so the derived list reflects enabled providers rather than only providers with activity; the empty-
providerscase degrades safely (chart renders an empty plot, table renders three columns). - Noted but did not report: sources from stale (contract-version-mismatched) environments still contribute to the derived provider list while contributing nothing to totals, which can surface an all-zero provider column. This is an edge case of existing merge semantics rather than a regression introduced here.
🔍 Agent activity — 1 tool call
💭 I'm checking for edge cases now—the loading skeleton still references a fixed provider order which could cause a minor layout jump, and I need to verify what happens when there are no providers at all, since the chart and breakdown table's column span logic depend on that count. Tracing through the colSpan math confirms it's correct (providers.length + 3 matches the header columns), and the empty-providers case degrades gracefully with the chart just showing a flat baseline. I'm now wondering whether the model breakdown table might leak in models from disabled providers, and whether the legend/tooltip/axis all consistently derive from the same providers array—they do. The remaining question is whether a provider that's enabled server-side but has no actual session data still shows up correctly in the UI. I'm checking if there's a layout shift during loading when the skeleton shows both providers but only one remains after data loads, and confirming whether an all-error scenario correctly renders empty rows rather than breaking. I also want to verify the test file still works with the default PROVIDER_ORDER parameter, and I'm grepping for other consumers of UsageProviderChart and checking the type imports in usageProviders.ts to make sure everything lines up with the UI consistency rules around shared controls and visual preservation. Now I'm looking at the empty-state behavior of the chart when providers is empty, confirming the grid layout column is fine, and checking other call sites of UsageProviderChart to see if a newly required prop would break typechecking. I'm flagging a potential layout shift issue: the skeleton still renders both provider rows regardless of which providers are disabled, so during loading a user with one disabled provider would see two skeleton rows collapse into one once the real content loads—worth checking against the "static stand-in matching loaded shape" comment in the code. Other edge cases seem fine: an empty providers array just shows totals without breaking anything, and providers with no transcript data being hidden entirely from the UI seems like reasonable, even desirable, behavior rather than a bug. One thing worth checking is whether
providersgets a new array identity on every render sinceenvironmentsmight not be stable, which would invalidate the chart's memoization and force unnecessary recomputation of paths each time. I should trace howuseUsageproducesenvironmentsto see if that's actually memoized. I'm digging into whether the "providers" derivation logic actually causes a real UI issue—checking if disabled providers get excluded from scanning entirely, which would mean the model breakdown table naturally omits their data without needing special filtering. I don't see a legend-related problem either, since the tooltip already covers that role. One genuine inconsistency I'm spotting: the skeleton's provider row count is static while the loaded page's row count now depends on which providers actually have sources, so disabling a provider could cause a visible layout shift between skeleton and loaded state. That's a real but minor finding worth noting, though I should verify the chart's indexing logic stays consistent between providers and columns before concluding anything, and then check the added test for correctness. I'm checking whether the mobile app needs a similar usage page, but that's out of scope since it's not part of the apps/web surface. I'm also verifying that the totals column stays consistent with the providers list across multi-environment setups where a provider might be enabled in one environment but disabled in another, and double-checking the skeleton state for mismatches. Now I'm digging into a subtler edge case: could merged totals include a provider that isn't reflected in the sources list, or vice versa — a stale environment contributing a provider column that shows all zeros because it's excluded from the merged totals but still counted when deriving the providers list? I want to trace through the UsageSummary contract and the merge logic to see how staleness and duplicate environments are actually handled. Deriving from sources rather than merged data has two edge behaviors worth noting: enabled providers with missing transcript directories still show up as a zero column, which actually seems like correct behavior since it reflects "enabled" status regardless of whether data exists. But stale environments' sources still count toward the provider list while contributing nothing to the totals, which could be misleading. There's also a skeleton mismatch issue — the skeleton always renders both provider rows, but if one provider is disabled, the loaded page will only show one row, causing a layout shift of about 48px when data settles. This is a pre-existing pattern that the patch makes more relevant since previously the skeleton always matched exactly. Fixing it is tricky since the set of enabled providers isn't known until settling completes, though partial summaries from already-loaded environments could potentially be used to narrow the skeleton earlier. I'm also checking other edge cases: when all providers are disabled, the chart area renders empty with just totals, which isn't a regression since it's expected behavior. The chart tooltip correctly falls back to showing only the "Total" row when there are no provider paths. I confirmed the mobile app's usage page still references the old provider ordering, but that's out of scope since only the web app is included in this patch. The model breakdown table's column span of four remains correct and unaffected. Checking the time-breakdown table, the header and body cell counts both equal providers plus three, so the colSpan values line up correctly. The providers memo properly skips environments that errored since their summary is null, and providers with a "missing" status are correctly hidden when a source like claude is disabled, which is the intended behavior. So the real candidates worth flagging are the skeleton mismatch and the stale-environment edge case. Looking closer at the skeleton issue, there's a genuine visual regression: the skeleton always reserves two provider rows while loading, but after data loads there might only be one, causing a layout shift — which directly contradicts the file's own design intent that nothing should move once data arrives. With this change, anyone with a disabled provider will see the page visibly reflow, which is a real layout-preservation regression. A workable fix would be to pass the skeleton whatever providers are already known from any environment that's answered, falling back to PROVIDER_ORDER only when nothing has loaded yet — though during a fully pending state with zero known providers, rendering no rows would look even worse than the mismatch itself. I think this is a legitimate but modest finding worth an inline comment on the skeleton usage, though I need to check whether the affected line falls within the diff I'm allowed to comment on. GitHub only accepts review comments on lines visible in the diff hunks, and the skeleton's actual render line sits outside the changed hunks in this file — the nearby hunks cover the memoization addition and a few other spots, but not that line. So I'd need to anchor the comment near the memo hunk instead, since that's within the diff context, and consider whether this issue is significant enough to flag as a failing check. I'm also double-checking other potential issues: the empty-providers edge case can't actually occur given the data flow, the chart's memo dependencies look correct, and the type mismatch between the mutable provider order array and the readonly chart prop isn't actually a problem since arrays are assignable there. I'm weighing whether the skeleton/loaded-shape mismatch is significant enough to flag as a real regression versus a minor cosmetic issue, since the guidance suggests only failing for clear consistency or regression risks. I'm leaning toward including it as a finding but still deciding whether it warrants failing the check outright or just a comment, so I want to verify whether this behavior is actually new or pre-existing before deciding. Given the guidance that this check defaults to failure unless there are no findings, I lean toward neutral rather than a hard failure since this is a borderline case. Let me double-check the colSpan logic to see if there's a more definitive issue — if providers is empty, colSpan becomes 3, which should still align with the column count. I'm also verifying the remaining imports and usages are all correct — useMemo, the provider kind type, and PROVIDER_ORDER all check out fine, and nothing else in the web app renders provider legends that I'd need to worry about. I'm ready to finalize and add the inline comment at the relevant lines in UsagePage.tsx. Since the skeleton mismatch fix spans both the call site and the component itself, I'll skip a diff suggestion and instead describe the fix idea in prose — passing the derived providers (falling back to PROVIDER_ORDER) into UsageSkeleton. I'll target the comment around lines 54-62 on the new file side, then post the review.
1. modify_pr — One finding: the loading skeleton no longer matches the loaded page shape once a provider is hidden. See the inline comment.
Agent Credits: 126 credits