Skip to content

feat(usage): show subscription limits

MacroscopeApp / Macroscope - UI Consistency failed Aug 26, 2026 in 3m 57s

UI Consistency: 1 issue found

apps/web/src/components/usage/UsagePage.tsx — The provider summary row was reshaped (color-dot swatch removed, gap-5/gap-1 -> gap-6/gap-1.5, new share line and quota meters) but UsageSkeleton still mirrors the previous shape: it keeps a size-2 dot placeholder that no longer exists, uses the old gaps, has no placeholder for the meters, and still sizes the chart block at h-56 while UsageProviderChart is now min-h-60. Since the skeleton is documented as a static stand-in so blocks "fill in exactly once", the content now shifts horizontally and vertically when environments finish reporting. Inline comment posted with the suggested skeleton update.

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.

Reviewed in-scope files: apps/web/src/components/usage/UsagePage.tsx, apps/web/src/components/usage/UsageProviderChart.tsx, apps/web/src/components/usage/UsagePage.test.tsx (mobile, server, contracts and shared changes used only as context, since they fall outside the configured include patterns).

Checked and found acceptable:

  • Tooltip/TooltipTrigger/TooltipPopup come from components/ui/tooltip, and rendering the trigger as a non-interactive div via render matches existing repo precedent (ThreadErrorBanner, ProviderStatusBanner, pullRequestPresentation).
  • No Progress primitive exists in components/ui, so the inline track/fill is not a reconstruction of an existing primitive; the only other web meter (chat/ContextWindowMeter) is a popover-specific bar with intentionally different geometry.
  • The progressbar span is a grid item (blockified), so h-1/rounded-full/overflow-hidden apply; aria-label, aria-valuemin/max, aria-valuenow (omitted for the indeterminate/unlimited case) and aria-valuetext are coherent.
  • timeZone={window.timeZone} resolves to the locally destructured usage window, not the global window; formatDateTimeShort and formatUsageResetCountdown accept ISO strings.
  • Chart height ownership change (min-h-60 flex-1 plus lg:h-full on the column) preserves axis-label and plot stretch behavior in both the single-column and lg two-column layouts.
  • Provider colors are read from PROVIDER_PRESENTATION (including the var(--contrast-foreground) token for Codex), so theme tokens are preserved in the meter fill and glow.

🔍 Agent activity — 14 tool calls

💭 I'm checking whether window.timeZone in UsagePage.tsx refers to the global browser window object or a local variable, since if it's the global object this would be a bug where the timezone value is undefined. I also want to verify formatDateTimeShort is properly imported, since the diff only shows formatUsageResetCountdown being added. And I'm noting the removal of the color dot swatch from provider rows appears consistent between web and mobile versions. Let me pull up the actual file to confirm these details.

1. browse_code — apps/web/src/components/usage/UsagePage.tsx

2. browse_code — apps/web/src/components/ui/tooltip*

💭 I'm confirming the window destructuring resolves correctly in both components, then checking whether the UI library already has a Progress primitive I should reuse instead of hand-rolling a progressbar span. I'm also looking into tooltip.tsx to see if TooltipTrigger supports a render prop. I notice the skeleton loader still renders a swatch dot placeholder even though the patch removed that dot from the actual loaded provider rows, which is a stale/inconsistent visual mismatch worth flagging. There are also minor spacing discrepancies between skeleton and real content (gap-5 vs gap-6, gap-1 vs gap-1.5) that are worth a note. I'm also weighing whether removing the color dot hurts the chart-to-provider color mapping, but since ProviderMark brand icons remain, that seems like an intentional design tradeoff rather than a bug. Turning to the Tooltip implementation, I want to verify whether the Progress primitive exists in the UI library and whether TooltipTrigger rendering as a plain div breaks keyboard accessibility since divs aren't focusable by default. Since aria-valuetext already carries the reset info for screen readers, this seems more like a moderate accessibility gap for keyboard-only sighted users than a full violation, especially since the tooltip's exact reset time isn't shown elsewhere in the visible UI. I want to check how Tooltip is used elsewhere in the codebase to see if this non-focusable trigger pattern is already an established convention.

3. browse_code — apps/web/src/components/ui/tooltip.tsx

4. browse_code — apps/web/src/components/ui/*.tsx

💭 There's no Progress primitive, so a raw progressbar span works fine, and Tooltip doesn't strictly need a Provider. But rendering the TooltipTrigger as a div instead of the default button loses keyboard focusability, so I should check how other call sites in the repo handle tooltip triggers on non-button elements to see if they add tabIndex.

5. git_tools — TooltipTrigger

💭 Non-interactive trigger elements using div or span are already an established pattern elsewhere in the repo, so that's not a real finding. I'm now checking whether the timezone reference inside UsagePage's map is properly scoped and not shadowed, then moving on to examine how UsageLimitMeters handles its window/limits properties for potential regressions. The bigger concern I'm spotting is that summary providers with subscription limits but no usage still render fine with default zero values, but removing the color swatch from the summary list breaks the visual link between chart colors and provider rows — since color dots now only appear on limit meters, providers without quota data (like Claude or Gemini without limits) lose their color association with the chart entirely, which looks like a genuine visual regression. I also want to check whether UsageProviderChart has its own legend, and flag that the skeleton still shows the old dot placeholder even though the loaded layout dropped it, creating a mismatch between skeleton and content. Additionally, the chart's height handling changed from fixed h-56 to min-h-56 with flex-1, which could behave inconsistently below the lg breakpoint since the row height is now driven by the left column instead of being fixed. I'm also noting web/mobile styling differences are expected since mobile is out of scope, and the polling interval re-renders the whole usage page every 60 seconds regardless of whether meters are shown, which is a minor perf concern rather than a real consistency issue. Now I want to double-check the resetsAt formatting call signature against how sinceTime is used elsewhere, so I'm looking at UsageProviderChart to check the legend and formatDateTimeShort usage.

6. browse_code — apps/web/src/components/usage/UsageProviderChart.tsx

7. git_tools — export function formatDateTimeShort

💭 I'm checking if the chart has a legend elsewhere, like in the tooltip content, since the summary rows' color dot appears to have been the only static provider-to-color mapping—removing it could break that association for providers without subscription limits. The question is whether this counts as a real regression: the author swapped the color swatch for a brand ProviderMark icon, so I need to check if that icon uses currentColor (monochrome) or retains actual brand colors—if it's monochrome, the color legend is genuinely lost, so I should look at usageProviders.tsx to confirm.

8. browse_code — apps/web/src/components/usage/usageProviders.tsx

9. browse_code — apps/web/src/components/usage/UsageProviderChart.tsx

💭 The chart tooltip already only shows the mark icon without a color dot, so removing the dot from summary rows actually matches that existing pattern. That means the provider-colored lines rely entirely on mark colors for identification, so I should check PROVIDER_PRESENTATION to confirm whether the marks are brand-colored.

10. browse_code — apps/web/src/components/usage/*

11. browse_code — apps/web/src/components/usage/usageProviders.ts

💭 I notice the Codex color uses a CSS variable applied inline for the meter fill and glow effect, which works fine since it's read within the element's scope regardless of the underlying color format. Now I'm turning to how the color dot removal should be handled. Looking at the test file, since the tooltip components get mocked as plain divs, the render prop passed through isn't valid for real DOM elements—React would likely drop it with a warning since it's an object, but the test still passes since it only checks for markup content like aria-labels. That said, this means the mocked tooltip doesn't actually validate the real Tooltip composition, which matters if there's a guideline requiring focused tests when primitive composition affects behavior. I'm now scrutinizing the grid layout more closely: the trigger div uses column sizing of 2.25rem for the label columns, and Base UI's Tooltip.Trigger merges its own data attributes and handlers onto the custom render element, which is a pattern already established elsewhere in the repo. One real concern is the progressbar span—since spans are inline by default and only get blockified inside flex or grid containers, not standalone, this could cause a layout mismatch if the span isn't sitting in the right context. Then I confirm the span is indeed a direct grid child, so it gets blockified automatically and the height utility applies correctly. I move on to checking the padding-left value used for alignment against the provider mark's icon size plus gap, which checks out consistently, and I note some minor typographic choices like a 9px countdown text size and reduced opacity muted foreground color that seem like small aesthetic decisions rather than real issues. The one substantive concern I land on is that the loading skeleton is documented as a fixed, non-shimmering placeholder meant to match the loaded page's exact shape, but this PR altered the provider row's structure — removing the status dot, adjusting gap spacing, and adding an extra line plus meters — which likely means the skeleton no longer matches the new layout. That interval re-render probably isn't worth flagging as a real UI consistency issue, so I'll skip it. Checking accessibility further, the aria-valuetext and hint text seem fine, and the indeterminate progressbar case with no aria-valuenow is valid ARIA usage when min/max are present. I'll move on to checking the tooltip implementation next. Since Tooltip works fine without a provider elsewhere, that's not a concern. Thinking through the layout more, the right column's h-full is redundant given grid stretch behavior, but the real regression risk is that the chart now stretches to match the left column's height, which could grow tall with multiple meters and providers, changing how the chart renders compared to its previous fixed height. Below the lg breakpoint though, the stacked flex-column layout means the chart div's flex-1 with min-h-60 will settle around 15rem tall since the parent has auto height and the item's basis resolves against the min-height floor — a slight increase from the old fixed 14rem, but not a meaningful regression. I trace the row-flex children — the plot svg stretches to fill the container's height via align-items stretch, and the y-axis label div positions ticks by percentage so it scales fine too. The real issue I keep coming back to is the skeleton loader height not matching the final rendered chart height, causing a layout shift, though it's a minor one rather than a hard failure. I want to check if there's something more significant going on with how the tests validate this. Checking the window destructuring inside the provider map — no shadowing issues there, and mergeUsage always populates subscriptionLimits so that path looks safe too. I'm now considering whether the mobile version of the usage page has similar issues. The mobile RouteScreen file is out of scope since only apps/web is included, so I can't flag that path even though its ordering logic differs slightly from web's. Turning back to web, I notice a limits-only provider will show a zeroed-out row (0% cost, 0 tokens) since totals is undefined, but that seems to be an intentional design choice per the usage docs, so I don't think it's a bug worth reporting. Now I want to check the tooltip's accessibility handling. On touch devices, since the tooltip only opens on hover/focus, tapping a non-interactive div won't reveal the exact reset time - but since the visible countdown text and aria-valuetext already convey that information to assistive tech, I don't think this counts as an actual accessibility gap given the repo's existing patterns. I'm now scanning for potential truncation or overflow issues in the grid layout classes. Checking through the various container widths, grid-cols templates, padding, and gap values in the meters and provider blocks, everything looks consistent and shouldn't cause layout problems - the box-shadow glow being web-only is fine since it's cross-platform by design. The main finding I'm settling on is that the skeleton loader no longer matches the actual summary row's shape - it still has an old dot placeholder, is missing the second line and meters, and uses different spacing and heights than the real content, which would cause a visible layout shift that defeats the whole purpose of having a skeleton. I'm also noticing the test mocks out the tooltip's TooltipTrigger as a plain div, which means the render prop element with the grid classes never actually renders in the test - so the test is validating a DOM structure that diverges from what's rendered in production, and the render prop composition itself goes unvalidated. That's worth flagging as a minor test-quality gap, and I should also check whether passing an object to a DOM prop like this would even be valid under React 19's stricter prop handling. The check's default behavior seems to be failure when findings exist, only reporting "all clear" when there's nothing to flag—so if this skeleton mismatch is real, it should count as a failure. Let me verify the mismatch is genuine by comparing the actual summary row layout (flex column with dot, circle, label, sessions count, and a share percentage line) against the skeleton's structure, which uses a similar dot-and-circle pattern but different spacing. Comparing more carefully, the new summary row dropped the dot placeholder and shifted to different gap and padding values, while the skeleton still includes that now-removed dot and the old gap spacing—meaning the loading state and loaded state would visually jump by roughly 16 pixels horizontally plus a vertical gap shift. Since the whole point of skeletons is to prevent this kind of layout shift, this looks like a legitimate, narrow finding caused by the diff. I'm also noting a secondary sizing mismatch between the skeleton chart placeholder's fixed height and the loaded chart's minimum height, which is worth mentioning alongside the first issue, though I'll skip further exploration into tooltip reachability and the provider chart's flex growth behavior since those seem fine on closer look. Let me check whether the two meter implementations in web and mobile could instead reuse a shared progress-bar primitive, so I'll search for existing role="progressbar" usage in the web codebase.

12. git_tools — progressbar

💭 There's an existing ContextWindowMeter component with similar track/fill/tooltip treatment — I want to check if it shares the same pattern as the new UsageLimitMeters, since the rule favors a named primitive when multiple call sites repeat the same geometry.

13. browse_code — apps/web/src/components/chat/ContextWindowMeter.tsx

💭 I'm noticing the new usage meter duplicates the existing ContextWindowMeter's bar styling but with inconsistent sizing and opacity, and it's missing the transition animation the original has—worth flagging as repeated styling that should probably be unified. Given the intentional geometry differences and only two consumers, I decide this abstraction suggestion is too borderline and risks being noise, so I'll drop it and stick to concrete issues instead. I take one more pass checking for regressions around the skeleton and removed dot, and verify that PROVIDER_PRESENTATION covers every provider key in the usage contract to rule out a missing-case bug. I also check the countdown row's fallback text — when resetsAt is null the row correctly shows "Reset time unavailable" rather than a broken countdown, so that's fine. Deciding my one real inline comment should target the skeleton mismatch, I now need to confirm whether GitHub's inline review comments can even land on those unchanged lines, since they fall outside the diff hunk. Since line 707 sits outside the diff hunks and GitHub rejects comments outside them, I'll attach the comment instead to a changed line within the diff, like the provider mapping div around line 264, and reference the skeleton lines in the comment text itself. The comment I'm drafting explains that the loading skeleton still mirrors the old summary row layout — it has a leftover color-dot placeholder, mismatched gap spacing compared to the updated row, and no placeholder reserved for the new meters section, which will cause a layout shift once real data loads. I'm suggesting the fix should drop the dot and align the gaps and minimum height with the current row and chart styling. I'm also weighing whether to flag that the new meter fill lacks a smooth width transition, unlike the existing web meter which animates with transition-[width,...] and respects reduced-motion preferences — worth noting since these values refresh periodically. I confirm the skeleton has an extra 16px of leading placeholder from its size-2 dot plus gap that the new row's size-4 mark doesn't replicate, so that's settled. I'll go with a single inline comment and post the review now.

14. modify_pr — One finding in apps/web/src/components/usage/UsagePage.tsx: the loading skeleton was not updated alongside the reshaped provider summary row, so the page still shifts when data lands. Details inline.


Agent Credits: 216 credits