feat(sessions): context breakdown popover#2353
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
8b5cff8 to
b68e4e4
Compare
7032aef to
78a3903
Compare
b68e4e4 to
ebe1dbc
Compare
78a3903 to
00f8802
Compare
Replaces the simple tooltip on the inline context indicator with a
Radix popover that mirrors the design spec: an aggregate header
("~74K / 200K tokens", "37% full"), a multi-segment progress bar
colored by source, and a per-category token list (System prompt,
Tools, Rules, Skills, MCP, Subagents, Conversation) with zero-token
rows hidden. The inline label now reads "X/Y · Z%" so the percent
is glanceable without opening the popover.
The popover consumes the new `breakdown` field from `useContextUsage`
(plumbed in B3). When the breakdown is missing — e.g. before the
first response of a session — the popover falls back to a single
percent bar and surfaces a one-line placeholder explaining that
detail will arrive after the first response. Color choices and the
`formatTokensCompact` helper are lifted into a shared
`contextColors` module so both the indicator and popover read from
the same source.
Generated-By: PostHog Code
Task-Id: bac06178-1ab1-4000-9a56-1901215bd4af
Generated-By: PostHog Code
Task-Id: bac06178-1ab1-4000-9a56-1901215bd4af
ebe1dbc to
da7e14b
Compare
00f8802 to
80a75e8
Compare
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
apps/code/src/renderer/features/sessions/components/ContextUsageIndicator.tsx:27-32
`Popover.Trigger` renders a `<button>` element by default. Wrapping another `<button>` inside it produces a `<button>` inside a `<button>`, which is invalid HTML — browsers strip the outer element and break the Radix focus/aria wiring, so the popover may not open reliably and screen readers will see a confusing interactive structure. Add `asChild` so Radix reuses the inner button as the trigger directly.
```suggestion
<Popover.Trigger asChild>
<button
type="button"
className="flex cursor-pointer select-none items-center gap-1 bg-transparent"
aria-label={`Context usage: ${percentage}%`}
>
```
### Issue 2 of 2
apps/code/src/renderer/features/sessions/components/ContextBreakdownPopover.test.tsx:22-41
The first two tests render exactly the same component (`usageWith(null)`) — the `render` call is duplicated. Merging them into a single `null`-breakdown test (or using a parameterised table for `breakdown = null` vs `breakdown = {...}`) removes the redundancy and aligns with the team's preference for parameterised tests.
```suggestion
it("renders the header and placeholder copy when breakdown is null", () => {
render(
<Theme>
<ContextBreakdownPopover usage={usageWith(null)} />
</Theme>,
);
expect(screen.getByText(/74K \/ 200K tokens/)).toBeInTheDocument();
expect(screen.getByText("37% full")).toBeInTheDocument();
expect(
screen.getByText(/Detailed breakdown available after the first response/),
).toBeInTheDocument();
});
```
Reviews (1): Last reviewed commit: "feat(sessions): context breakdown popove..." | Re-trigger Greptile |
| <Popover.Trigger> | ||
| <button | ||
| type="button" | ||
| className="flex cursor-pointer select-none items-center gap-1 bg-transparent" | ||
| aria-label={`Context usage: ${percentage}%`} | ||
| > |
There was a problem hiding this comment.
Popover.Trigger renders a <button> element by default. Wrapping another <button> inside it produces a <button> inside a <button>, which is invalid HTML — browsers strip the outer element and break the Radix focus/aria wiring, so the popover may not open reliably and screen readers will see a confusing interactive structure. Add asChild so Radix reuses the inner button as the trigger directly.
| <Popover.Trigger> | |
| <button | |
| type="button" | |
| className="flex cursor-pointer select-none items-center gap-1 bg-transparent" | |
| aria-label={`Context usage: ${percentage}%`} | |
| > | |
| <Popover.Trigger asChild> | |
| <button | |
| type="button" | |
| className="flex cursor-pointer select-none items-center gap-1 bg-transparent" | |
| aria-label={`Context usage: ${percentage}%`} | |
| > |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/sessions/components/ContextUsageIndicator.tsx
Line: 27-32
Comment:
`Popover.Trigger` renders a `<button>` element by default. Wrapping another `<button>` inside it produces a `<button>` inside a `<button>`, which is invalid HTML — browsers strip the outer element and break the Radix focus/aria wiring, so the popover may not open reliably and screen readers will see a confusing interactive structure. Add `asChild` so Radix reuses the inner button as the trigger directly.
```suggestion
<Popover.Trigger asChild>
<button
type="button"
className="flex cursor-pointer select-none items-center gap-1 bg-transparent"
aria-label={`Context usage: ${percentage}%`}
>
```
How can I resolve this? If you propose a fix, please make it concise.| it("renders the header with aggregate tokens", () => { | ||
| render( | ||
| <Theme> | ||
| <ContextBreakdownPopover usage={usageWith(null)} /> | ||
| </Theme>, | ||
| ); | ||
| expect(screen.getByText(/74K \/ 200K tokens/)).toBeInTheDocument(); | ||
| expect(screen.getByText("37% full")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("shows the placeholder copy when breakdown is missing", () => { | ||
| render( | ||
| <Theme> | ||
| <ContextBreakdownPopover usage={usageWith(null)} /> | ||
| </Theme>, | ||
| ); | ||
| expect( | ||
| screen.getByText(/Detailed breakdown available after the first response/), | ||
| ).toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
The first two tests render exactly the same component (
usageWith(null)) — the render call is duplicated. Merging them into a single null-breakdown test (or using a parameterised table for breakdown = null vs breakdown = {...}) removes the redundancy and aligns with the team's preference for parameterised tests.
| it("renders the header with aggregate tokens", () => { | |
| render( | |
| <Theme> | |
| <ContextBreakdownPopover usage={usageWith(null)} /> | |
| </Theme>, | |
| ); | |
| expect(screen.getByText(/74K \/ 200K tokens/)).toBeInTheDocument(); | |
| expect(screen.getByText("37% full")).toBeInTheDocument(); | |
| }); | |
| it("shows the placeholder copy when breakdown is missing", () => { | |
| render( | |
| <Theme> | |
| <ContextBreakdownPopover usage={usageWith(null)} /> | |
| </Theme>, | |
| ); | |
| expect( | |
| screen.getByText(/Detailed breakdown available after the first response/), | |
| ).toBeInTheDocument(); | |
| }); | |
| it("renders the header and placeholder copy when breakdown is null", () => { | |
| render( | |
| <Theme> | |
| <ContextBreakdownPopover usage={usageWith(null)} /> | |
| </Theme>, | |
| ); | |
| expect(screen.getByText(/74K \/ 200K tokens/)).toBeInTheDocument(); | |
| expect(screen.getByText("37% full")).toBeInTheDocument(); | |
| expect( | |
| screen.getByText(/Detailed breakdown available after the first response/), | |
| ).toBeInTheDocument(); | |
| }); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/sessions/components/ContextBreakdownPopover.test.tsx
Line: 22-41
Comment:
The first two tests render exactly the same component (`usageWith(null)`) — the `render` call is duplicated. Merging them into a single `null`-breakdown test (or using a parameterised table for `breakdown = null` vs `breakdown = {...}`) removes the redundancy and aligns with the team's preference for parameterised tests.
```suggestion
it("renders the header and placeholder copy when breakdown is null", () => {
render(
<Theme>
<ContextBreakdownPopover usage={usageWith(null)} />
</Theme>,
);
expect(screen.getByText(/74K \/ 200K tokens/)).toBeInTheDocument();
expect(screen.getByText("37% full")).toBeInTheDocument();
expect(
screen.getByText(/Detailed breakdown available after the first response/),
).toBeInTheDocument();
});
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Problem
The context usage indicator previously showed only a tooltip with raw token counts. Users had no way to see how their context window was being consumed across different categories (system prompt, tools, rules, skills, MCP, subagents, conversation).
Changes
ContextUsageIndicatorwith a RadixPopoverthat opens aContextBreakdownPopoverpanel when clicked.ContextBreakdownPopoverdisplays the aggregate token count, percentage full, a visual progress bar, and a per-category breakdown with color-coded segments when breakdown data is available. Before the first response, a placeholder message is shown instead.getOverallUsageColor,formatTokensCompact, andCONTEXT_CATEGORIES) intocontextColors.tsso they can be reused across both components.74K/200K · 37%).How did you test this?
Added unit tests in
ContextBreakdownPopover.test.tsxcovering:null.Publish to changelog?
no