Skip to content

feat(sessions): context breakdown popover#2353

Open
k11kirky wants to merge 1 commit into
posthog-code/context-breakdown-datafrom
posthog-code/context-breakdown-popover
Open

feat(sessions): context breakdown popover#2353
k11kirky wants to merge 1 commit into
posthog-code/context-breakdown-datafrom
posthog-code/context-breakdown-popover

Conversation

@k11kirky
Copy link
Copy Markdown
Contributor

@k11kirky k11kirky commented May 25, 2026

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

  • Replaced the tooltip on ContextUsageIndicator with a Radix Popover that opens a ContextBreakdownPopover panel when clicked.
  • ContextBreakdownPopover displays 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.
  • The progress bar renders as a segmented, multi-color bar when breakdown data exists, or a single-color bar based on overall usage level otherwise.
  • Extracted shared utilities (getOverallUsageColor, formatTokensCompact, and CONTEXT_CATEGORIES) into contextColors.ts so they can be reused across both components.
  • The indicator text now also shows the percentage inline (e.g. 74K/200K · 37%).

How did you test this?

Added unit tests in ContextBreakdownPopover.test.tsx covering:

  • Header rendering with aggregate token counts and percentage.
  • Placeholder copy when breakdown data is null.
  • Only non-zero categories appearing in the breakdown list.

Publish to changelog?

no

Copy link
Copy Markdown
Contributor Author

k11kirky commented May 25, 2026

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
@k11kirky k11kirky force-pushed the posthog-code/context-breakdown-popover branch from ebe1dbc to da7e14b Compare May 26, 2026 09:11
@k11kirky k11kirky force-pushed the posthog-code/context-breakdown-data branch from 00f8802 to 80a75e8 Compare May 26, 2026 09:11
@k11kirky k11kirky marked this pull request as ready for review May 26, 2026 09:20
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 26, 2026

Prompt To Fix All With AI
Fix 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

Comment on lines +27 to 32
<Popover.Trigger>
<button
type="button"
className="flex cursor-pointer select-none items-center gap-1 bg-transparent"
aria-label={`Context usage: ${percentage}%`}
>
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.

P1 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.

Suggested change
<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.

Comment on lines +22 to +41
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();
});
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.

P2 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.

Suggested change
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!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant