fix(web): usage totals no longer jump while devices report in - #5772

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks
Aug 9, 2026
Merged

fix(web): usage totals no longer jump while devices report in#5772
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Opening the usage page with several connected devices was gross: totals rendered as soon as the first device answered, then every number on the page revised itself as each remaining device landed.

The page now holds its content until every device is terminal (reported, failed, or stale). While waiting it keeps the header toggles live and shows a static skeleton in the page's final shape, topped with a per-device strip: reported devices get a check, failed ones an x, and still-scanning ones a duty-cycled pulse. The strip only appears with two or more devices; a single-device user just sees the skeleton. Failed and stale devices never block the gate, and changing the window or hitting refresh restarts it.

No shimmer or continuous animation; the skeleton is static and the scanning pulse reuses the existing duty-cycled status-pulse keyframes.

Built by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
UI-only loading and presentation changes on the usage page; no changes to how usage is fetched or merged.

Overview
Fixes jumping usage numbers when several connected environments report at different times. The page no longer renders merged totals while any device is still scanning; it waits until every environment is terminal (success, error, or excluded as stale).

While loading, the header stays interactive and the body shows a static skeleton shaped like the final layout (no shimmer). With two or more devices, a per-device strip shows checkmarks for finished devices, an X for failures, and a pulsed label for devices still scanning, plus a count of devices left.

Coverage messaging moves behind the gate: the “partial totals” line is removed because in-progress devices never show real numbers anymore. Failed, stale, and duplicate-transcript warnings still appear once the page settles.

Reviewed by Cursor Bugbot for commit e7aa04d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix usage totals jumping on the Usage page while devices report in

  • Replaces the previous partial-totals display with a static skeleton layout while usage data is still collecting from environments, preventing layout shifts as devices report in.
  • Adds a UsageDeviceStrip component (shown when multiple environments are present) that displays per-device status (completed, failed, or scanning) and a count of devices still scanning.
  • UsageCoverageNotice no longer shows a 'totals are partial' message; it only renders after all environments reach a terminal state, covering failures, stale environments, and duplicate sources.
  • The UsageSkeleton uses fixed bar heights (SKELETON_BAR_HEIGHTS) to avoid layout shifts and is replaced atomically when data is ready.
📊 Macroscope summarized e7aa04d. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

ghost commented Aug 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c596fa78-a3ed-45f3-94bf-28f402e443b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 9, 2026
// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumusage/UsagePage.tsx:43

The settling gate only treats an environment as still reporting when summary is null, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, useUsage retains each environment's previous successful summary while marking its query as waiting, so every refreshed environment still counts as answered (isPending and isPartial both stay false) and settling is never set. The gate should also consider each EnvironmentUsageStatus's in-flight/waiting state — not just absence of a cached summary — so the skeleton shows until every refresh round-trip completes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/usage/UsagePage.tsx around line 43:
The `settling` gate only treats an environment as still reporting when `summary` is `null`, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, `useUsage` retains each environment's previous successful `summary` while marking its query as waiting, so every refreshed environment still counts as answered (`isPending` and `isPartial` both stay false) and `settling` is never set. The gate should also consider each `EnvironmentUsageStatus`'s in-flight/waiting state — not just absence of a cached `summary` — so the skeleton shows until every refresh round-trip completes.

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh skips settling gate

Medium Severity

settling only follows isPending / isPartial, which treat an environment as done once it has a non-null summary. Usage queries use SWR, so refresh keeps prior summaries while waiting is true. Hitting refresh therefore never re-enters the skeleton gate, and merged totals jump again as each device’s refresh lands—the same issue this change aims to stop.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

@github-actions

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB−1 B (−0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+7 B (+0.1%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−8 B (−0.1%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.4 KiB11.3 KiB−8 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB−5 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire5.9 KiB5.9 KiB−3 B (−0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 288d8e3 · PR result: e7aa04d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Two unresolved findings identify that the settling gate doesn't handle the refresh case - when refreshing, cached summaries are retained so the skeleton never shows, allowing totals to jump again. The Macroscope finding at Medium severity meets the repository's blocking threshold.

You can customize Macroscope's approvability policy. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(web): usage totals no longer jump while devices report in - #5772

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks
Aug 9, 2026
Merged

fix(web): usage totals no longer jump while devices report in#5772
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Opening the usage page with several connected devices was gross: totals rendered as soon as the first device answered, then every number on the page revised itself as each remaining device landed.

The page now holds its content until every device is terminal (reported, failed, or stale). While waiting it keeps the header toggles live and shows a static skeleton in the page's final shape, topped with a per-device strip: reported devices get a check, failed ones an x, and still-scanning ones a duty-cycled pulse. The strip only appears with two or more devices; a single-device user just sees the skeleton. Failed and stale devices never block the gate, and changing the window or hitting refresh restarts it.

No shimmer or continuous animation; the skeleton is static and the scanning pulse reuses the existing duty-cycled status-pulse keyframes.

Built by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
UI-only loading and presentation changes on the usage page; no changes to how usage is fetched or merged.

Overview
Fixes jumping usage numbers when several connected environments report at different times. The page no longer renders merged totals while any device is still scanning; it waits until every environment is terminal (success, error, or excluded as stale).

While loading, the header stays interactive and the body shows a static skeleton shaped like the final layout (no shimmer). With two or more devices, a per-device strip shows checkmarks for finished devices, an X for failures, and a pulsed label for devices still scanning, plus a count of devices left.

Coverage messaging moves behind the gate: the “partial totals” line is removed because in-progress devices never show real numbers anymore. Failed, stale, and duplicate-transcript warnings still appear once the page settles.

Reviewed by Cursor Bugbot for commit e7aa04d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix usage totals jumping on the Usage page while devices report in

  • Replaces the previous partial-totals display with a static skeleton layout while usage data is still collecting from environments, preventing layout shifts as devices report in.
  • Adds a UsageDeviceStrip component (shown when multiple environments are present) that displays per-device status (completed, failed, or scanning) and a count of devices still scanning.
  • UsageCoverageNotice no longer shows a 'totals are partial' message; it only renders after all environments reach a terminal state, covering failures, stale environments, and duplicate sources.
  • The UsageSkeleton uses fixed bar heights (SKELETON_BAR_HEIGHTS) to avoid layout shifts and is replaced atomically when data is ready.
📊 Macroscope summarized e7aa04d. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

ghost commented Aug 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c596fa78-a3ed-45f3-94bf-28f402e443b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 9, 2026
// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumusage/UsagePage.tsx:43

The settling gate only treats an environment as still reporting when summary is null, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, useUsage retains each environment's previous successful summary while marking its query as waiting, so every refreshed environment still counts as answered (isPending and isPartial both stay false) and settling is never set. The gate should also consider each EnvironmentUsageStatus's in-flight/waiting state — not just absence of a cached summary — so the skeleton shows until every refresh round-trip completes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/usage/UsagePage.tsx around line 43:
The `settling` gate only treats an environment as still reporting when `summary` is `null`, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, `useUsage` retains each environment's previous successful `summary` while marking its query as waiting, so every refreshed environment still counts as answered (`isPending` and `isPartial` both stay false) and `settling` is never set. The gate should also consider each `EnvironmentUsageStatus`'s in-flight/waiting state — not just absence of a cached `summary` — so the skeleton shows until every refresh round-trip completes.

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh skips settling gate

Medium Severity

settling only follows isPending / isPartial, which treat an environment as done once it has a non-null summary. Usage queries use SWR, so refresh keeps prior summaries while waiting is true. Hitting refresh therefore never re-enters the skeleton gate, and merged totals jump again as each device’s refresh lands—the same issue this change aims to stop.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

@github-actions

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB−1 B (−0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+7 B (+0.1%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−8 B (−0.1%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.4 KiB11.3 KiB−8 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB−5 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire5.9 KiB5.9 KiB−3 B (−0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 288d8e3 · PR result: e7aa04d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Two unresolved findings identify that the settling gate doesn't handle the refresh case - when refreshing, cached summaries are retained so the skeleton never shows, allowing totals to jump again. The Macroscope finding at Medium severity meets the repository's blocking threshold.

You can customize Macroscope's approvability policy. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): usage totals no longer jump while devices report in - #5772

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks
Aug 9, 2026
Merged

fix(web): usage totals no longer jump while devices report in#5772
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Opening the usage page with several connected devices was gross: totals rendered as soon as the first device answered, then every number on the page revised itself as each remaining device landed.

The page now holds its content until every device is terminal (reported, failed, or stale). While waiting it keeps the header toggles live and shows a static skeleton in the page's final shape, topped with a per-device strip: reported devices get a check, failed ones an x, and still-scanning ones a duty-cycled pulse. The strip only appears with two or more devices; a single-device user just sees the skeleton. Failed and stale devices never block the gate, and changing the window or hitting refresh restarts it.

No shimmer or continuous animation; the skeleton is static and the scanning pulse reuses the existing duty-cycled status-pulse keyframes.

Built by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
UI-only loading and presentation changes on the usage page; no changes to how usage is fetched or merged.

Overview
Fixes jumping usage numbers when several connected environments report at different times. The page no longer renders merged totals while any device is still scanning; it waits until every environment is terminal (success, error, or excluded as stale).

While loading, the header stays interactive and the body shows a static skeleton shaped like the final layout (no shimmer). With two or more devices, a per-device strip shows checkmarks for finished devices, an X for failures, and a pulsed label for devices still scanning, plus a count of devices left.

Coverage messaging moves behind the gate: the “partial totals” line is removed because in-progress devices never show real numbers anymore. Failed, stale, and duplicate-transcript warnings still appear once the page settles.

Reviewed by Cursor Bugbot for commit e7aa04d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix usage totals jumping on the Usage page while devices report in

  • Replaces the previous partial-totals display with a static skeleton layout while usage data is still collecting from environments, preventing layout shifts as devices report in.
  • Adds a UsageDeviceStrip component (shown when multiple environments are present) that displays per-device status (completed, failed, or scanning) and a count of devices still scanning.
  • UsageCoverageNotice no longer shows a 'totals are partial' message; it only renders after all environments reach a terminal state, covering failures, stale environments, and duplicate sources.
  • The UsageSkeleton uses fixed bar heights (SKELETON_BAR_HEIGHTS) to avoid layout shifts and is replaced atomically when data is ready.
📊 Macroscope summarized e7aa04d. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

ghost commented Aug 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c596fa78-a3ed-45f3-94bf-28f402e443b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 9, 2026
// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumusage/UsagePage.tsx:43

The settling gate only treats an environment as still reporting when summary is null, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, useUsage retains each environment's previous successful summary while marking its query as waiting, so every refreshed environment still counts as answered (isPending and isPartial both stay false) and settling is never set. The gate should also consider each EnvironmentUsageStatus's in-flight/waiting state — not just absence of a cached summary — so the skeleton shows until every refresh round-trip completes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/usage/UsagePage.tsx around line 43:
The `settling` gate only treats an environment as still reporting when `summary` is `null`, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, `useUsage` retains each environment's previous successful `summary` while marking its query as waiting, so every refreshed environment still counts as answered (`isPending` and `isPartial` both stay false) and `settling` is never set. The gate should also consider each `EnvironmentUsageStatus`'s in-flight/waiting state — not just absence of a cached `summary` — so the skeleton shows until every refresh round-trip completes.

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh skips settling gate

Medium Severity

settling only follows isPending / isPartial, which treat an environment as done once it has a non-null summary. Usage queries use SWR, so refresh keeps prior summaries while waiting is true. Hitting refresh therefore never re-enters the skeleton gate, and merged totals jump again as each device’s refresh lands—the same issue this change aims to stop.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

@github-actions

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB−1 B (−0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+7 B (+0.1%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−8 B (−0.1%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.4 KiB11.3 KiB−8 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB−5 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire5.9 KiB5.9 KiB−3 B (−0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 288d8e3 · PR result: e7aa04d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Two unresolved findings identify that the settling gate doesn't handle the refresh case - when refreshing, cached summaries are retained so the skeleton never shows, allowing totals to jump again. The Macroscope finding at Medium severity meets the repository's blocking threshold.

You can customize Macroscope's approvability policy. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): usage totals no longer jump while devices report in - #5772

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks
Aug 9, 2026
Merged

fix(web): usage totals no longer jump while devices report in#5772
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Opening the usage page with several connected devices was gross: totals rendered as soon as the first device answered, then every number on the page revised itself as each remaining device landed.

The page now holds its content until every device is terminal (reported, failed, or stale). While waiting it keeps the header toggles live and shows a static skeleton in the page's final shape, topped with a per-device strip: reported devices get a check, failed ones an x, and still-scanning ones a duty-cycled pulse. The strip only appears with two or more devices; a single-device user just sees the skeleton. Failed and stale devices never block the gate, and changing the window or hitting refresh restarts it.

No shimmer or continuous animation; the skeleton is static and the scanning pulse reuses the existing duty-cycled status-pulse keyframes.

Built by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
UI-only loading and presentation changes on the usage page; no changes to how usage is fetched or merged.

Overview
Fixes jumping usage numbers when several connected environments report at different times. The page no longer renders merged totals while any device is still scanning; it waits until every environment is terminal (success, error, or excluded as stale).

While loading, the header stays interactive and the body shows a static skeleton shaped like the final layout (no shimmer). With two or more devices, a per-device strip shows checkmarks for finished devices, an X for failures, and a pulsed label for devices still scanning, plus a count of devices left.

Coverage messaging moves behind the gate: the “partial totals” line is removed because in-progress devices never show real numbers anymore. Failed, stale, and duplicate-transcript warnings still appear once the page settles.

Reviewed by Cursor Bugbot for commit e7aa04d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix usage totals jumping on the Usage page while devices report in

  • Replaces the previous partial-totals display with a static skeleton layout while usage data is still collecting from environments, preventing layout shifts as devices report in.
  • Adds a UsageDeviceStrip component (shown when multiple environments are present) that displays per-device status (completed, failed, or scanning) and a count of devices still scanning.
  • UsageCoverageNotice no longer shows a 'totals are partial' message; it only renders after all environments reach a terminal state, covering failures, stale environments, and duplicate sources.
  • The UsageSkeleton uses fixed bar heights (SKELETON_BAR_HEIGHTS) to avoid layout shifts and is replaced atomically when data is ready.
📊 Macroscope summarized e7aa04d. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

ghost commented Aug 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c596fa78-a3ed-45f3-94bf-28f402e443b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 9, 2026
// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumusage/UsagePage.tsx:43

The settling gate only treats an environment as still reporting when summary is null, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, useUsage retains each environment's previous successful summary while marking its query as waiting, so every refreshed environment still counts as answered (isPending and isPartial both stay false) and settling is never set. The gate should also consider each EnvironmentUsageStatus's in-flight/waiting state — not just absence of a cached summary — so the skeleton shows until every refresh round-trip completes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/usage/UsagePage.tsx around line 43:
The `settling` gate only treats an environment as still reporting when `summary` is `null`, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, `useUsage` retains each environment's previous successful `summary` while marking its query as waiting, so every refreshed environment still counts as answered (`isPending` and `isPartial` both stay false) and `settling` is never set. The gate should also consider each `EnvironmentUsageStatus`'s in-flight/waiting state — not just absence of a cached `summary` — so the skeleton shows until every refresh round-trip completes.

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh skips settling gate

Medium Severity

settling only follows isPending / isPartial, which treat an environment as done once it has a non-null summary. Usage queries use SWR, so refresh keeps prior summaries while waiting is true. Hitting refresh therefore never re-enters the skeleton gate, and merged totals jump again as each device’s refresh lands—the same issue this change aims to stop.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

@github-actions

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB−1 B (−0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+7 B (+0.1%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−8 B (−0.1%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.4 KiB11.3 KiB−8 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB−5 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire5.9 KiB5.9 KiB−3 B (−0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 288d8e3 · PR result: e7aa04d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Two unresolved findings identify that the settling gate doesn't handle the refresh case - when refreshing, cached summaries are retained so the skeleton never shows, allowing totals to jump again. The Macroscope finding at Medium severity meets the repository's blocking threshold.

You can customize Macroscope's approvability policy. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(web): usage totals no longer jump while devices report in - #5772

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks
Aug 9, 2026
Merged

fix(web): usage totals no longer jump while devices report in#5772
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Opening the usage page with several connected devices was gross: totals rendered as soon as the first device answered, then every number on the page revised itself as each remaining device landed.

The page now holds its content until every device is terminal (reported, failed, or stale). While waiting it keeps the header toggles live and shows a static skeleton in the page's final shape, topped with a per-device strip: reported devices get a check, failed ones an x, and still-scanning ones a duty-cycled pulse. The strip only appears with two or more devices; a single-device user just sees the skeleton. Failed and stale devices never block the gate, and changing the window or hitting refresh restarts it.

No shimmer or continuous animation; the skeleton is static and the scanning pulse reuses the existing duty-cycled status-pulse keyframes.

Built by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
UI-only loading and presentation changes on the usage page; no changes to how usage is fetched or merged.

Overview
Fixes jumping usage numbers when several connected environments report at different times. The page no longer renders merged totals while any device is still scanning; it waits until every environment is terminal (success, error, or excluded as stale).

While loading, the header stays interactive and the body shows a static skeleton shaped like the final layout (no shimmer). With two or more devices, a per-device strip shows checkmarks for finished devices, an X for failures, and a pulsed label for devices still scanning, plus a count of devices left.

Coverage messaging moves behind the gate: the “partial totals” line is removed because in-progress devices never show real numbers anymore. Failed, stale, and duplicate-transcript warnings still appear once the page settles.

Reviewed by Cursor Bugbot for commit e7aa04d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix usage totals jumping on the Usage page while devices report in

  • Replaces the previous partial-totals display with a static skeleton layout while usage data is still collecting from environments, preventing layout shifts as devices report in.
  • Adds a UsageDeviceStrip component (shown when multiple environments are present) that displays per-device status (completed, failed, or scanning) and a count of devices still scanning.
  • UsageCoverageNotice no longer shows a 'totals are partial' message; it only renders after all environments reach a terminal state, covering failures, stale environments, and duplicate sources.
  • The UsageSkeleton uses fixed bar heights (SKELETON_BAR_HEIGHTS) to avoid layout shifts and is replaced atomically when data is ready.
📊 Macroscope summarized e7aa04d. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

ghost commented Aug 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c596fa78-a3ed-45f3-94bf-28f402e443b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 9, 2026
// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumusage/UsagePage.tsx:43

The settling gate only treats an environment as still reporting when summary is null, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, useUsage retains each environment's previous successful summary while marking its query as waiting, so every refreshed environment still counts as answered (isPending and isPartial both stay false) and settling is never set. The gate should also consider each EnvironmentUsageStatus's in-flight/waiting state — not just absence of a cached summary — so the skeleton shows until every refresh round-trip completes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/usage/UsagePage.tsx around line 43:
The `settling` gate only treats an environment as still reporting when `summary` is `null`, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, `useUsage` retains each environment's previous successful `summary` while marking its query as waiting, so every refreshed environment still counts as answered (`isPending` and `isPartial` both stay false) and `settling` is never set. The gate should also consider each `EnvironmentUsageStatus`'s in-flight/waiting state — not just absence of a cached `summary` — so the skeleton shows until every refresh round-trip completes.

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh skips settling gate

Medium Severity

settling only follows isPending / isPartial, which treat an environment as done once it has a non-null summary. Usage queries use SWR, so refresh keeps prior summaries while waiting is true. Hitting refresh therefore never re-enters the skeleton gate, and merged totals jump again as each device’s refresh lands—the same issue this change aims to stop.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

@github-actions

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB−1 B (−0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+7 B (+0.1%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−8 B (−0.1%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.4 KiB11.3 KiB−8 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB−5 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire5.9 KiB5.9 KiB−3 B (−0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 288d8e3 · PR result: e7aa04d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Two unresolved findings identify that the settling gate doesn't handle the refresh case - when refreshing, cached summaries are retained so the skeleton never shows, allowing totals to jump again. The Macroscope finding at Medium severity meets the repository's blocking threshold.

You can customize Macroscope's approvability policy. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): usage totals no longer jump while devices report in - #5772

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks
Aug 9, 2026
Merged

fix(web): usage totals no longer jump while devices report in#5772
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Opening the usage page with several connected devices was gross: totals rendered as soon as the first device answered, then every number on the page revised itself as each remaining device landed.

The page now holds its content until every device is terminal (reported, failed, or stale). While waiting it keeps the header toggles live and shows a static skeleton in the page's final shape, topped with a per-device strip: reported devices get a check, failed ones an x, and still-scanning ones a duty-cycled pulse. The strip only appears with two or more devices; a single-device user just sees the skeleton. Failed and stale devices never block the gate, and changing the window or hitting refresh restarts it.

No shimmer or continuous animation; the skeleton is static and the scanning pulse reuses the existing duty-cycled status-pulse keyframes.

Built by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
UI-only loading and presentation changes on the usage page; no changes to how usage is fetched or merged.

Overview
Fixes jumping usage numbers when several connected environments report at different times. The page no longer renders merged totals while any device is still scanning; it waits until every environment is terminal (success, error, or excluded as stale).

While loading, the header stays interactive and the body shows a static skeleton shaped like the final layout (no shimmer). With two or more devices, a per-device strip shows checkmarks for finished devices, an X for failures, and a pulsed label for devices still scanning, plus a count of devices left.

Coverage messaging moves behind the gate: the “partial totals” line is removed because in-progress devices never show real numbers anymore. Failed, stale, and duplicate-transcript warnings still appear once the page settles.

Reviewed by Cursor Bugbot for commit e7aa04d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix usage totals jumping on the Usage page while devices report in

  • Replaces the previous partial-totals display with a static skeleton layout while usage data is still collecting from environments, preventing layout shifts as devices report in.
  • Adds a UsageDeviceStrip component (shown when multiple environments are present) that displays per-device status (completed, failed, or scanning) and a count of devices still scanning.
  • UsageCoverageNotice no longer shows a 'totals are partial' message; it only renders after all environments reach a terminal state, covering failures, stale environments, and duplicate sources.
  • The UsageSkeleton uses fixed bar heights (SKELETON_BAR_HEIGHTS) to avoid layout shifts and is replaced atomically when data is ready.
📊 Macroscope summarized e7aa04d. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

ghost commented Aug 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c596fa78-a3ed-45f3-94bf-28f402e443b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 9, 2026
// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumusage/UsagePage.tsx:43

The settling gate only treats an environment as still reporting when summary is null, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, useUsage retains each environment's previous successful summary while marking its query as waiting, so every refreshed environment still counts as answered (isPending and isPartial both stay false) and settling is never set. The gate should also consider each EnvironmentUsageStatus's in-flight/waiting state — not just absence of a cached summary — so the skeleton shows until every refresh round-trip completes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/usage/UsagePage.tsx around line 43:
The `settling` gate only treats an environment as still reporting when `summary` is `null`, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, `useUsage` retains each environment's previous successful `summary` while marking its query as waiting, so every refreshed environment still counts as answered (`isPending` and `isPartial` both stay false) and `settling` is never set. The gate should also consider each `EnvironmentUsageStatus`'s in-flight/waiting state — not just absence of a cached `summary` — so the skeleton shows until every refresh round-trip completes.

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh skips settling gate

Medium Severity

settling only follows isPending / isPartial, which treat an environment as done once it has a non-null summary. Usage queries use SWR, so refresh keeps prior summaries while waiting is true. Hitting refresh therefore never re-enters the skeleton gate, and merged totals jump again as each device’s refresh lands—the same issue this change aims to stop.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

@github-actions

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB−1 B (−0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+7 B (+0.1%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−8 B (−0.1%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.4 KiB11.3 KiB−8 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB−5 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire5.9 KiB5.9 KiB−3 B (−0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 288d8e3 · PR result: e7aa04d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Two unresolved findings identify that the settling gate doesn't handle the refresh case - when refreshing, cached summaries are retained so the skeleton never shows, allowing totals to jump again. The Macroscope finding at Medium severity meets the repository's blocking threshold.

You can customize Macroscope's approvability policy. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(web): usage totals no longer jump while devices report in - #5772

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks
Aug 9, 2026
Merged

fix(web): usage totals no longer jump while devices report in#5772
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Opening the usage page with several connected devices was gross: totals rendered as soon as the first device answered, then every number on the page revised itself as each remaining device landed.

The page now holds its content until every device is terminal (reported, failed, or stale). While waiting it keeps the header toggles live and shows a static skeleton in the page's final shape, topped with a per-device strip: reported devices get a check, failed ones an x, and still-scanning ones a duty-cycled pulse. The strip only appears with two or more devices; a single-device user just sees the skeleton. Failed and stale devices never block the gate, and changing the window or hitting refresh restarts it.

No shimmer or continuous animation; the skeleton is static and the scanning pulse reuses the existing duty-cycled status-pulse keyframes.

Built by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
UI-only loading and presentation changes on the usage page; no changes to how usage is fetched or merged.

Overview
Fixes jumping usage numbers when several connected environments report at different times. The page no longer renders merged totals while any device is still scanning; it waits until every environment is terminal (success, error, or excluded as stale).

While loading, the header stays interactive and the body shows a static skeleton shaped like the final layout (no shimmer). With two or more devices, a per-device strip shows checkmarks for finished devices, an X for failures, and a pulsed label for devices still scanning, plus a count of devices left.

Coverage messaging moves behind the gate: the “partial totals” line is removed because in-progress devices never show real numbers anymore. Failed, stale, and duplicate-transcript warnings still appear once the page settles.

Reviewed by Cursor Bugbot for commit e7aa04d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix usage totals jumping on the Usage page while devices report in

  • Replaces the previous partial-totals display with a static skeleton layout while usage data is still collecting from environments, preventing layout shifts as devices report in.
  • Adds a UsageDeviceStrip component (shown when multiple environments are present) that displays per-device status (completed, failed, or scanning) and a count of devices still scanning.
  • UsageCoverageNotice no longer shows a 'totals are partial' message; it only renders after all environments reach a terminal state, covering failures, stale environments, and duplicate sources.
  • The UsageSkeleton uses fixed bar heights (SKELETON_BAR_HEIGHTS) to avoid layout shifts and is replaced atomically when data is ready.
📊 Macroscope summarized e7aa04d. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

ghost commented Aug 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c596fa78-a3ed-45f3-94bf-28f402e443b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 9, 2026
// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumusage/UsagePage.tsx:43

The settling gate only treats an environment as still reporting when summary is null, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, useUsage retains each environment's previous successful summary while marking its query as waiting, so every refreshed environment still counts as answered (isPending and isPartial both stay false) and settling is never set. The gate should also consider each EnvironmentUsageStatus's in-flight/waiting state — not just absence of a cached summary — so the skeleton shows until every refresh round-trip completes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/usage/UsagePage.tsx around line 43:
The `settling` gate only treats an environment as still reporting when `summary` is `null`, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, `useUsage` retains each environment's previous successful `summary` while marking its query as waiting, so every refreshed environment still counts as answered (`isPending` and `isPartial` both stay false) and `settling` is never set. The gate should also consider each `EnvironmentUsageStatus`'s in-flight/waiting state — not just absence of a cached `summary` — so the skeleton shows until every refresh round-trip completes.

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh skips settling gate

Medium Severity

settling only follows isPending / isPartial, which treat an environment as done once it has a non-null summary. Usage queries use SWR, so refresh keeps prior summaries while waiting is true. Hitting refresh therefore never re-enters the skeleton gate, and merged totals jump again as each device’s refresh lands—the same issue this change aims to stop.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

@github-actions

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB−1 B (−0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+7 B (+0.1%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−8 B (−0.1%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.4 KiB11.3 KiB−8 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB−5 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire5.9 KiB5.9 KiB−3 B (−0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 288d8e3 · PR result: e7aa04d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Two unresolved findings identify that the settling gate doesn't handle the refresh case - when refreshing, cached summaries are retained so the skeleton never shows, allowing totals to jump again. The Macroscope finding at Medium severity meets the repository's blocking threshold.

You can customize Macroscope's approvability policy. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(web): usage totals no longer jump while devices report in - #5772

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks
Aug 9, 2026
Merged

fix(web): usage totals no longer jump while devices report in#5772
t3dotgg merged 1 commit into
mainfrom
t3code/usage-device-loading-mocks

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Opening the usage page with several connected devices was gross: totals rendered as soon as the first device answered, then every number on the page revised itself as each remaining device landed.

The page now holds its content until every device is terminal (reported, failed, or stale). While waiting it keeps the header toggles live and shows a static skeleton in the page's final shape, topped with a per-device strip: reported devices get a check, failed ones an x, and still-scanning ones a duty-cycled pulse. The strip only appears with two or more devices; a single-device user just sees the skeleton. Failed and stale devices never block the gate, and changing the window or hitting refresh restarts it.

No shimmer or continuous animation; the skeleton is static and the scanning pulse reuses the existing duty-cycled status-pulse keyframes.

Built by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
UI-only loading and presentation changes on the usage page; no changes to how usage is fetched or merged.

Overview
Fixes jumping usage numbers when several connected environments report at different times. The page no longer renders merged totals while any device is still scanning; it waits until every environment is terminal (success, error, or excluded as stale).

While loading, the header stays interactive and the body shows a static skeleton shaped like the final layout (no shimmer). With two or more devices, a per-device strip shows checkmarks for finished devices, an X for failures, and a pulsed label for devices still scanning, plus a count of devices left.

Coverage messaging moves behind the gate: the “partial totals” line is removed because in-progress devices never show real numbers anymore. Failed, stale, and duplicate-transcript warnings still appear once the page settles.

Reviewed by Cursor Bugbot for commit e7aa04d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix usage totals jumping on the Usage page while devices report in

  • Replaces the previous partial-totals display with a static skeleton layout while usage data is still collecting from environments, preventing layout shifts as devices report in.
  • Adds a UsageDeviceStrip component (shown when multiple environments are present) that displays per-device status (completed, failed, or scanning) and a count of devices still scanning.
  • UsageCoverageNotice no longer shows a 'totals are partial' message; it only renders after all environments reach a terminal state, covering failures, stale environments, and duplicate sources.
  • The UsageSkeleton uses fixed bar heights (SKELETON_BAR_HEIGHTS) to avoid layout shifts and is replaced atomically when data is ready.
📊 Macroscope summarized e7aa04d. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

ghost commented Aug 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c596fa78-a3ed-45f3-94bf-28f402e443b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 9, 2026
// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumusage/UsagePage.tsx:43

The settling gate only treats an environment as still reporting when summary is null, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, useUsage retains each environment's previous successful summary while marking its query as waiting, so every refreshed environment still counts as answered (isPending and isPartial both stay false) and settling is never set. The gate should also consider each EnvironmentUsageStatus's in-flight/waiting state — not just absence of a cached summary — so the skeleton shows until every refresh round-trip completes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/usage/UsagePage.tsx around line 43:
The `settling` gate only treats an environment as still reporting when `summary` is `null`, so clicking Refresh leaves the loaded totals visible and lets each environment's refreshed data pop in individually as responses arrive. During a refresh, `useUsage` retains each environment's previous successful `summary` while marking its query as waiting, so every refreshed environment still counts as answered (`isPending` and `isPartial` both stay false) and `settling` is never set. The gate should also consider each `EnvironmentUsageStatus`'s in-flight/waiting state — not just absence of a cached `summary` — so the skeleton shows until every refresh round-trip completes.

ghost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

// Hold the content until every environment is terminal. Rendering merged
// totals while devices are still answering makes every number on the page
// jump as each one lands.
const settling = isPending || isPartial;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh skips settling gate

Medium Severity

settling only follows isPending / isPartial, which treat an environment as done once it has a non-null summary. Usage queries use SWR, so refresh keeps prior summaries while waiting is true. Hitting refresh therefore never re-enters the skeleton gate, and merged totals jump again as each device’s refresh lands—the same issue this change aims to stop.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e7aa04d. Configure here.

@github-actions

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire11.3 KiB11.3 KiB−1 B (−0.0%)15.1 KiB
CodexThread snapshot wire5.5 KiB5.5 KiB+7 B (+0.1%)7.3 KiB
CodexLive turn WebSocket wire5.9 KiB5.9 KiB−8 B (−0.1%)7.8 KiB
CodexLive turn WebSocket decoded49.7 KiB49.7 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire11.4 KiB11.3 KiB−8 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire5.5 KiB5.5 KiB−5 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire5.9 KiB5.9 KiB−3 B (−0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded50.6 KiB50.6 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 288d8e3 · PR result: e7aa04d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

ghost commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Two unresolved findings identify that the settling gate doesn't handle the refresh case - when refreshing, cached summaries are retained so the skeleton never shows, allowing totals to jump again. The Macroscope finding at Medium severity meets the repository's blocking threshold.

You can customize Macroscope's approvability policy. Learn more.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg