Skip to content

feat(agents): context-usage ring gauge + composition breakdown#4596

Open
kevin-dp wants to merge 13 commits into
feat/context-compactionfrom
feat/context-usage-indicator-ui
Open

feat(agents): context-usage ring gauge + composition breakdown#4596
kevin-dp wants to merge 13 commits into
feat/context-compactionfrom
feat/context-usage-indicator-ui

Conversation

@kevin-dp

Copy link
Copy Markdown
Contributor

Improves the composer-footer context-usage indicator (built on the Phase 0 gauge from the base branch). Two commits:

1. Circular ring gauge

Replaces the flat dot before the X% label with a small SVG donut whose arc fills proportionally to context-window usage. Track + progress arc both use currentColor, so the existing normal/warning/critical level colour tints it for free.

2. Composition breakdown popover

Hovering the indicator reveals a per-part breakdown of the prompt — a stacked bar + legend showing how much of the window the system prompt, tool definitions, conversation messages, and free space each occupy (modelled on Claude Code's /context).

How the data works:

  • The runtime estimates the stable request parts ({ system, tools }, char/4 via approxTokens) once per model call and persists them on the step as a new additive context_breakdown column, next to the real cache-inclusive context_input_tokens.
  • The UI derives the Messages bucket as real total − system − tools, and Free as window − total, so the segments always sum to the gauge even though the system/tools figures are approximations (the panel notes this).
  • Shared, testable helpers computeContextBreakdown / parseContextBreakdown keep the math in token-accountant and out of the component.

Files

  • entity-schema: additive context_breakdown string column on steps.
  • outbound-bridge / pi-adapter: compute + persist the estimate.
  • token-accountant: breakdown helpers + types, exported from the client entry (+ 4 unit tests).
  • UI: ContextUsageRing, ContextUsageDetails (HoverCard popover), wired into ContextUsageIndicator.

Testing

  • Full runtime suite green (1024 passed); UI tsc clean.
  • Verified live in the desktop app — ring fills correctly and the hover breakdown renders.

Note on base

Targets feat/context-compaction because it extends the Phase 0 gauge that only exists there. Re-target to main once the compaction PR merges.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Electric Agents Desktop Builds

Build artifacts for commit ac19f7d.

Platform Status Artifact
macOS Apple Silicon Passed DMG
macOS Intel Passed DMG
Windows x64 Passed Installer
Linux x64 Passed AppImage / deb

Workflow run

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.62025% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.90%. Comparing base (28ce457) to head (ac19f7d).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...s-server-ui/src/components/ContextUsageDetails.tsx 0.00% 11 Missing ⚠️
...ents-server-ui/src/components/ContextUsageRing.tsx 0.00% 9 Missing ⚠️
...server-ui/src/components/ContextUsageIndicator.tsx 0.00% 4 Missing ⚠️
Additional details and impacted files
@@                     Coverage Diff                     @@
##           feat/context-compaction    #4596      +/-   ##
===========================================================
+ Coverage                    57.89%   57.90%   +0.01%     
===========================================================
  Files                          350      352       +2     
  Lines                        40644    40719      +75     
  Branches                     11828    11842      +14     
===========================================================
+ Hits                         23529    23578      +49     
- Misses                       17078    17104      +26     
  Partials                        37       37              
Flag Coverage Δ
packages/agents 72.75% <ø> (ø)
packages/agents-mobile 80.67% <ø> (ø)
packages/agents-runtime 83.23% <100.00%> (+0.02%) ⬆️
packages/agents-server 75.29% <ø> (-0.03%) ⬇️
packages/agents-server-ui 7.50% <0.00%> (-0.02%) ⬇️
packages/electric-ax 47.62% <ø> (ø)
typescript 57.90% <69.62%> (+0.01%) ⬆️
unit-tests 57.90% <69.62%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Electric Agents Mobile Build

Local mobile checks ran for commit ac19f7d.

The EAS Android preview build was skipped because the mobile-eas-build label is not present.
Add the mobile-eas-build label to this PR to produce an installable preview build.

Workflow run

@kevin-dp

Copy link
Copy Markdown
Contributor Author

🤖 Automated review — context-usage ring gauge + composition breakdown

Generated by a review agent. Severity-ranked; cite file:line.

Findings

[medium] — approxTokens(opts.tools) produces a near-meaningless tools estimatepi-adapter.ts via token-budget.ts
opts.tools is Array<AgentTool>, and the array branch of approxTokens only does char/4 on blocks whose type === 'text'; every other object contributes a flat 64. So "Tools" is effectively 64 × toolCount regardless of how large each tool's name/description/JSON-schema actually is — a real tool set is typically thousands of tokens, so this badly understates the tools bucket, which then silently inflates the "Messages" remainder. Suggest approxTokens(JSON.stringify(opts.tools)) (or serialize each tool's name + description + parameters). Worth fixing since the feature's whole value is the honesty of this split.

[low] — legend percents may not sum to 100%ContextUsageDetails.tsx + formatContextUsagePercent
Per-segment percents are each Math.rounded, so the four legend rows won't necessarily sum to 100%, and a non-zero segment under 0.5% renders a colored swatch + bar sliver labeled "0%". Cosmetic; consider a sub-percent format or a <1% floor for non-zero segments.

[low] — duplicated clamp logicContextUsageDetails.tsx
The headline recomputes usedRatio = min(1, used/window) locally while the trigger uses usage.ratio. They agree, but passing/using usage.ratio directly avoids drift.

[nit] — schema/persistence verified cleanentity-schema.ts / outbound-bridge.ts
context_breakdown column is additive (z.string().optional(), conditional spread on write) → backward-compatible; parseContextBreakdown is appropriately tolerant (malformed JSON → {}, non-number fields dropped).

[nit] — SVG ring + accessibility verified cleanContextUsageRing.tsx
dasharray/offset math correct (rotate(-90) starts at 12 o'clock, sweeps clockwise; ratio 0 renders nothing, 1 fills, pre-clamped to [0,1]); SVG is aria-hidden with the parent span carrying the aria-label; the stacked bar has role="img" + label.

Verified correct

  • computeContextBreakdown clamping is sound (used→[0,window], system→[0,used], tools→[0,used−system], messages=max(0,…), free=window−used); segments provably sum to the window and the used trio to usedTokens; window>0 ? … : 0 guard prevents NaN. Negative-clamp and missing-parts cases tested.
  • React: useMemo over [steps] recomputes correctly; stable unique key; returns null cleanly when there's no usage; HoverCard standalone usage matches the primitive's API; only one consumer (MessageInput.tsx).

Test gaps

  • No test for usedTokens > contextWindow (over-window clamp) or the tools-cap path where system alone exceeds used. No UI tests for the ring/details (consistent with the area).

Verification

  • npx vitest run token-accountant → 25 passed. agents-server-ui tsc --noEmit → clean.

Overall

Mergeable after addressing the tools-estimate issue. The math, clamping, SVG geometry, schema additivity, and parse tolerance are all correct and well-tested; the React/accessibility wiring is solid. The biggest real risk is the [medium]: approxTokens on the tool array degenerates to 64 × toolCount, so the "Tools" segment is essentially a constant and its shortfall is absorbed into "Messages" — the breakdown can look plausible while being materially wrong about where context is going, defeating the feature's purpose. Everything else is low/nit.

@kevin-dp kevin-dp force-pushed the feat/context-usage-indicator-ui branch 2 times, most recently from fa79f63 to e21fadd Compare June 17, 2026 09:59
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…view #4596]

`approxTokens(opts.tools)` hit approxTokens' array branch, which charges a flat
~64 per non-text block — so the "Tools" segment was ~64×toolCount regardless of
how large each tool's name/description/parameter schema actually is, and the
shortfall silently inflated the "Messages" remainder. Serialize the tool array
first so the estimate reflects what really occupies the prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…review #4596]

The popover header recomputed `min(1, usedTokens/contextWindow)` locally —
exactly what `computeContextUsage` already stored as `usage.ratio` (and what the
trigger gauge uses). Use it directly so the two can't drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…s [review #4596]

A segment with a small but non-zero share rendered a coloured swatch + bar
sliver labelled "0%", which reads as a contradiction. Label any non-zero
segment that rounds to 0% as "<1%" instead. (Per-segment rounding can still make
the four rows not sum to exactly 100% — acceptable for a composition popover;
exact apportionment isn't worth the complexity here.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…[review #4596]

The breakdown clamps `used` to the window so the segments still sum to the
window and `free` can't go negative when a step reports usage above the window.
That path was exercised but untested; lock it in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kevin-dp

Copy link
Copy Markdown
Contributor Author

Thanks — addressed the actionable findings, one commit each:

  • [medium] tools estimate5712883e7. Confirmed: approxTokens(opts.tools) hit the array branch's flat ~64-per-block fallback, so "Tools" was ~64×toolCount regardless of real schema size (and the shortfall inflated "Messages"). Now approxTokens(JSON.stringify(opts.tools)), which captures each tool's name + description + parameter schema — what actually occupies the prompt.

  • [low] tiny non-zero segment labelled "0%"922ff76c0. A non-zero segment that rounds to 0% now shows <1%, so a visible swatch/bar isn't labelled "0%". (Left exact sum-to-100% alone — that needs largest-remainder apportionment, overkill for a composition popover.)

  • [low] duplicated clamp8d95012c4. The header now uses usage.ratio (already min(1, used/window) from computeContextUsage) instead of recomputing it.

  • Test gap (over-window clamp)fac33da3a. Added a computeContextBreakdown case for usedTokens > contextWindow (used clamped to window, free = 0, segments still sum to the window). The tools-cap path (system ≥ used) was already covered by the existing "never produces negative messages" test.

The two [nit]s (schema additivity, SVG/accessibility) were verified-clean, so no changes there.

Full runtime suite green; UI typecheck + tests pass.

@claude

claude Bot commented Jun 17, 2026

Copy link
Copy Markdown

Claude Code Review

Summary

Adds a circular ring gauge plus a hover composition-breakdown popover to the agents-server-ui context-usage indicator, backed by a new additive context_breakdown estimate persisted per step. Well-scoped, well-tested, ships a changeset, and is mergeable. Since iteration 6 the branch was rebased; the only commits are the same three documentation-only trims previously reviewed — no behavioural change.

What's Working Well

  • Correct, well-tested breakdown math. computeContextBreakdown (token-accountant.ts:97-122) clamps in order — used→[0,window], system→[0,used], tools→[0,used−system], messages=max(0,used−system−tools), free=window−used — so the used trio provably sums to usedTokens and all four to the window even when part estimates are garbage. The over-window-clamp test locks down free >= 0.
  • Backward-compatible persistence. context_breakdown is z.string().optional() with a conditional spread on write; parseContextBreakdown is tolerant (malformed JSON → {}, non-number fields dropped). Older steps degrade cleanly to "messages absorbs everything."
  • Tools estimate is right. tools: approxTokens(JSON.stringify(opts.tools)) (pi-adapter.ts:325) routes through the string branch so the bucket reflects real schema size rather than the array branch's ~64×toolCount fallback.

Issues Found

Critical (Must Fix): None.

Important (Should Fix): None.

Suggestions (Nice to Have):

  • Serialize-first rationale is gone (carried from iteration 6, author's call). The comment explaining why JSON.stringify(opts.tools) is deliberate was dropped in ac19f7dff. The rationale is preserved in this PR's review history, but a future reader could "simplify" it back to a raw array and silently reintroduce the ~64×toolCount bug two rounds caught. Non-blocking.
  • Legend percents may not sum to 100% (already acknowledged). Per-segment Math.round means rows needn't total 100%; the <1% floor handles the worst cosmetic case. Reasonable to leave as-is.

Issue Conformance

No linked issue — a soft warning per convention, but the PR description is clear for a self-contained UI enhancement. Changeset (context-usage-breakdown.md, patch) present; client.ts additions are purely additive public-API exports (non-breaking). The PR still targets feat/context-compaction — re-target to main once the compaction PR merges so this doesn't merge to the wrong base.

Previous Review Status

All actionable findings from prior iterations remain resolved (tools estimate, <1% floor, usage.ratio reuse, over-window test, aria-label token/model detail, empty-bucket legend filter). The branch was rebased since iteration 6; the three new commits are the same doc-only trims, now 900f46520 / ed13b5659 / ac19f7dff. I verified the behavioural code — computeContextBreakdown, the schema, parseContextBreakdown, pi-adapter.ts tools estimate, the components, and the test assertions — is unchanged from the verified iteration-6 state. No new issues, no regressions.


Review iteration: 7 | 2026-06-18

@claude

claude Bot commented Jun 17, 2026

Copy link
Copy Markdown

Claude Code Review

Summary

Adds a circular SVG ring gauge to the composer-footer context-usage indicator plus a hover popover that breaks the prompt down into system / tools / messages / free, modelled on Claude Code's /context. TypeScript-only (agents-runtime + agents-server-ui). Clean, well-factored, and well-tested — this is mergeable.

What's Working Well

  • The math is genuinely sound. computeContextBreakdown clamps in the right order (used->[0,window], system->[0,used], tools->[0,used-system], messages=max(0,...), free=window-used) so the four segments provably sum to the window and the used trio sums to usedTokens — even when the part estimates are garbage or exceed the real total. The window > 0 ? ... : 0 guard avoids NaN. Exactly the "display can never contradict the gauge" property you want here.
  • Backward-compatible persistence. context_breakdown is an additive optional z.string().optional() column with a conditional spread on write, and parseContextBreakdown is appropriately tolerant (malformed JSON -> {}, non-number fields dropped). Older steps without the column stay valid and fall through to "messages absorbs everything" — the correct degradation.
  • SVG ring geometry is correct. dasharray/offset math, rotate(-90) to start at 12 o'clock and sweep clockwise, pre-clamp to [0,1], ratio 0 renders nothing / ratio 1 fills. aria-hidden on the SVG with the label on the parent span; the stacked bar carries role="img" + label.
  • Estimate honesty. Computing tools from JSON.stringify(opts.tools) rather than the raw AgentTool[] is the right call — approxTokens' array branch flat-charges ~64 per non-text block, so the raw array would degenerate to ~64 x toolCount and silently dump the shortfall into "Messages". The inline comment explaining this is excellent. The panel's "System & tools are estimates" note sets the right expectation.
  • Good test coverage of the helper: sum-to-window, missing parts, estimates-exceed-total, and the over-window clamp path are all covered.

Issues Found

Critical (Must Fix): None.

Important (Should Fix): None.

Suggestions (Nice to Have):

  • Zero-token legend rowsContextUsageDetails.tsx. The stacked bar omits zero-ratio segments (seg.ratio > 0 ? ... : null), but the legend always renders all four rows. For a step with no persisted breakdown (older events) this yields System prompt — 0 — 0% / Tools — 0 — 0% rows. Defensible as "always show the four categories", but if you'd rather not show empty buckets, gate the legend row the same way as the bar segment.
  • aria-label lost the token detailContextUsageIndicator.tsx. The previous indicator's aria-label embedded the full used / window · model string; it's now "Context used: X% — hover for breakdown". The detail lives in the HoverCard content (reachable on focus and labelled), so this is minor, but a non-hover/non-pointer user now gets only the percentage from the trigger itself. Consider keeping the token count in the trigger's label.
  • Legend percents needn't sum to 100% — each row is independently Math.rounded, so the four can read e.g. 5/15/30/49 = 99%. Cosmetic only; the <1% floor you added already handles the worst case (a visible sliver labelled "0%").

Issue Conformance

No linked issue (per the review context) — not unusual for an internal UI follow-up, but a tracking issue or reference would help. The PR description is unusually thorough and accurately matches the implementation, including the messages-as-remainder rationale and the base-branch note (targets feat/context-compaction; re-target to main after the compaction PR merges — worth not forgetting). A .changeset is present (both touched packages, patch), so the monorepo changeset convention is satisfied. The client.ts additions are purely additive public-API exports — non-breaking.

Note on prior automated review

The author's own automated review comment (2026-06-17) flagged a [medium] (tools estimated as 64 x toolCount), two [low]s (header recomputing usedRatio instead of reusing usage.ratio; sub-1% segments labelled "0%"), and a test gap (over-window clamp). All four are already resolved in the follow-up commits (5712883, 8d95012, 922ff76, fac33da) and the current diff reflects the fixes. Nothing from that review remains outstanding.


Review iteration: 1 | 2026-06-17

kevin-dp added a commit that referenced this pull request Jun 17, 2026
…bel [review #4596]

Switching the indicator to a HoverCard moved the token/window/model detail into
hover-only popover content, so the trigger's aria-label dropped to just the
percent — a small accessibility regression for keyboard/screen-reader users, who
could previously read those numbers from the old Tooltip's label. Restore the
`<used> / <window> tokens · <model>` summary to the trigger's own aria-label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…review #4596]

The stacked bar already skips zero-ratio segments, but the legend rendered all
four rows unconditionally — so a step with no persisted breakdown (older events)
showed noisy "System prompt — 0 — 0%" / "Tools — 0 — 0%" rows. Gate the legend
rows on `tokens > 0` to match the bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kevin-dp

Copy link
Copy Markdown
Contributor Author

Thanks for the two reviews — both flagged no critical/important issues. Addressed the two actionable suggestions, one commit each:

  • aria-label dropped the token/model detail (both reviews) → c252bba92. Moving to a hover-only HoverCard left the trigger's aria-label as just the percent, a small regression for keyboard/screen-reader users vs. the old Tooltip. The trigger now carries Context used: X% (<used> / <window> tokens · <model>) — hover for breakdown, so the essential numbers are available without hovering.

  • Zero-token legend rows (review 2) → 3caa02967. The stacked bar already skips zero-ratio segments, but the legend rendered all four rows — so older steps with no persisted breakdown showed noisy System prompt — 0 — 0% rows. Gated the legend on tokens > 0 to match the bar.

The "legend percents needn't sum to 100%" note is cosmetic and already mitigated by the <1% floor — left as-is per the earlier call (exact apportionment is overkill here).

UI typecheck + tests pass (111).

kevin-dp added a commit that referenced this pull request Jun 17, 2026
…view #4596]

`approxTokens(opts.tools)` hit approxTokens' array branch, which charges a flat
~64 per non-text block — so the "Tools" segment was ~64×toolCount regardless of
how large each tool's name/description/parameter schema actually is, and the
shortfall silently inflated the "Messages" remainder. Serialize the tool array
first so the estimate reflects what really occupies the prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…review #4596]

The popover header recomputed `min(1, usedTokens/contextWindow)` locally —
exactly what `computeContextUsage` already stored as `usage.ratio` (and what the
trigger gauge uses). Use it directly so the two can't drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…s [review #4596]

A segment with a small but non-zero share rendered a coloured swatch + bar
sliver labelled "0%", which reads as a contradiction. Label any non-zero
segment that rounds to 0% as "<1%" instead. (Per-segment rounding can still make
the four rows not sum to exactly 100% — acceptable for a composition popover;
exact apportionment isn't worth the complexity here.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kevin-dp kevin-dp force-pushed the feat/context-usage-indicator-ui branch from 3caa029 to 9e02ec4 Compare June 17, 2026 12:37
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…[review #4596]

The breakdown clamps `used` to the window so the segments still sum to the
window and `free` can't go negative when a step reports usage above the window.
That path was exercised but untested; lock it in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…bel [review #4596]

Switching the indicator to a HoverCard moved the token/window/model detail into
hover-only popover content, so the trigger's aria-label dropped to just the
percent — a small accessibility regression for keyboard/screen-reader users, who
could previously read those numbers from the old Tooltip's label. Restore the
`<used> / <window> tokens · <model>` summary to the trigger's own aria-label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 17, 2026
…review #4596]

The stacked bar already skips zero-ratio segments, but the legend rendered all
four rows unconditionally — so a step with no persisted breakdown (older events)
showed noisy "System prompt — 0 — 0%" / "Tools — 0 — 0%" rows. Gate the legend
rows on `tokens > 0` to match the bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@samwillis samwillis 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.

Reviewed the current head against feat/context-compaction and the previous review thread. The earlier findings are addressed (tools estimate serialization, ratio reuse, <1% tiny segments, over-window clamp test, aria-label detail, and empty legend buckets), and I didn’t find any blocking regressions. CI is green. Approving.

@kevin-dp kevin-dp force-pushed the feat/context-compaction branch from df21a6e to 295d9ab Compare June 18, 2026 10:11
kevin-dp added a commit that referenced this pull request Jun 18, 2026
…view #4596]

`approxTokens(opts.tools)` hit approxTokens' array branch, which charges a flat
~64 per non-text block — so the "Tools" segment was ~64×toolCount regardless of
how large each tool's name/description/parameter schema actually is, and the
shortfall silently inflated the "Messages" remainder. Serialize the tool array
first so the estimate reflects what really occupies the prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 18, 2026
…review #4596]

The popover header recomputed `min(1, usedTokens/contextWindow)` locally —
exactly what `computeContextUsage` already stored as `usage.ratio` (and what the
trigger gauge uses). Use it directly so the two can't drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 18, 2026
…s [review #4596]

A segment with a small but non-zero share rendered a coloured swatch + bar
sliver labelled "0%", which reads as a contradiction. Label any non-zero
segment that rounds to 0% as "<1%" instead. (Per-segment rounding can still make
the four rows not sum to exactly 100% — acceptable for a composition popover;
exact apportionment isn't worth the complexity here.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 18, 2026
…[review #4596]

The breakdown clamps `used` to the window so the segments still sum to the
window and `free` can't go negative when a step reports usage above the window.
That path was exercised but untested; lock it in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kevin-dp kevin-dp force-pushed the feat/context-usage-indicator-ui branch from 5fb6a8f to 87bfdb9 Compare June 18, 2026 10:16
kevin-dp added a commit that referenced this pull request Jun 18, 2026
…bel [review #4596]

Switching the indicator to a HoverCard moved the token/window/model detail into
hover-only popover content, so the trigger's aria-label dropped to just the
percent — a small accessibility regression for keyboard/screen-reader users, who
could previously read those numbers from the old Tooltip's label. Restore the
`<used> / <window> tokens · <model>` summary to the trigger's own aria-label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp added a commit that referenced this pull request Jun 18, 2026
…review #4596]

The stacked bar already skips zero-ratio segments, but the legend rendered all
four rows unconditionally — so a step with no persisted breakdown (older events)
showed noisy "System prompt — 0 — 0%" / "Tools — 0 — 0%" rows. Gate the legend
rows on `tokens > 0` to match the bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kevin-dp and others added 13 commits June 18, 2026 12:29
…icator

Replace the flat dot before the "X%" label with a small SVG donut whose arc
fills proportionally to the context-window usage. Track + progress arc both
use currentColor, so the existing normal/warning/critical level colour tints
the ring without extra wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ges / free)

Hovering the usage indicator now reveals a per-part composition of the prompt,
modelled on Claude Code's `/context`: a stacked bar + legend showing how much
of the window the system prompt, tool definitions, conversation messages, and
free space each occupy.

The runtime persists an approximate decomposition of the stable request parts:
pi-adapter estimates `{ system, tools }` token cost (char/4 via approxTokens)
once per call and writes it to the step as `context_breakdown` alongside the
cache-inclusive `context_input_tokens`. The UI derives the "messages" bucket as
the real total minus those estimates, so the segments always sum to the gauge
even though the part figures are approximate. New shared helpers
`computeContextBreakdown` / `parseContextBreakdown` in token-accountant keep the
math testable and out of the component.

- entity-schema: additive `context_breakdown` string column on steps.
- outbound-bridge / pi-adapter: compute + persist the estimate.
- token-accountant: breakdown helpers + types, exported from the client entry.
- UI: HoverCard popover (ContextUsageDetails) with a composition bar + legend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…view #4596]

`approxTokens(opts.tools)` hit approxTokens' array branch, which charges a flat
~64 per non-text block — so the "Tools" segment was ~64×toolCount regardless of
how large each tool's name/description/parameter schema actually is, and the
shortfall silently inflated the "Messages" remainder. Serialize the tool array
first so the estimate reflects what really occupies the prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…review #4596]

The popover header recomputed `min(1, usedTokens/contextWindow)` locally —
exactly what `computeContextUsage` already stored as `usage.ratio` (and what the
trigger gauge uses). Use it directly so the two can't drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s [review #4596]

A segment with a small but non-zero share rendered a coloured swatch + bar
sliver labelled "0%", which reads as a contradiction. Label any non-zero
segment that rounds to 0% as "<1%" instead. (Per-segment rounding can still make
the four rows not sum to exactly 100% — acceptable for a composition popover;
exact apportionment isn't worth the complexity here.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…[review #4596]

The breakdown clamps `used` to the window so the segments still sum to the
window and `free` can't go negative when a step reports usage above the window.
That path was exercised but untested; lock it in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bel [review #4596]

Switching the indicator to a HoverCard moved the token/window/model detail into
hover-only popover content, so the trigger's aria-label dropped to just the
percent — a small accessibility regression for keyboard/screen-reader users, who
could previously read those numbers from the old Tooltip's label. Restore the
`<used> / <window> tokens · <model>` summary to the trigger's own aria-label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…review #4596]

The stacked bar already skips zero-ratio segments, but the legend rendered all
four rows unconditionally — so a step with no persisted breakdown (older events)
showed noisy "System prompt — 0 — 0%" / "Tools — 0 — 0%" rows. Gate the legend
rows on `tokens > 0` to match the bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tighten the `context_breakdown` column comment and the tokenBreakdown intro to
be brief and to the point.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Trim the computeContextBreakdown JSDoc, the ContextUsageDetails component doc,
and an over-long test comment to be brief and to the point.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kevin-dp kevin-dp force-pushed the feat/context-usage-indicator-ui branch from 87bfdb9 to ac19f7d Compare June 18, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants