Skip to content

Commit aa7ca20

Browse files
author
王璨
committed
feat: add context window usage bar with session-switch
refresh Sync & archive context-window-usage-bar + fix-context-window-refresh-on-session-switch. - Add ContextWindowBar component in header center with token breakdown bar - Add `context_window` WebSocket event with throttled broadcast (500ms) - Persist bar update on saved session load (fix missing refresh on switch) - 8 color-coded categories: system, user, thinking, file read/edit, terminal, browser, other - Hover tooltip with per-category breakdown, minimum 2px segment width - Responsive: hidden below 768px
1 parent e8b3a2d commit aa7ca20

15 files changed

Lines changed: 502 additions & 1 deletion

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-06-19
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
## Context
2+
3+
The WebUI currently has a simple header with "DSCode" branding on the left, a model name label, and a theme toggle + connection status on the right. The center of the header is empty. Users have no visibility into their context window budget — how much of the model's token limit is occupied vs. free, or what categories of content consume tokens.
4+
5+
The `ContextManager` (`src/context/manager.ts`) already estimates total message tokens via `estimateMessagesTokens()` and uses that for compaction (`drop-oldest`, `sliding-window`). However, this estimation is aggregate-only; no per-category breakdown exists. The `WebUiBackend` tracks tool calls in `currentAssistant.tools` but doesn't expose token counts to the frontend.
6+
7+
The Cline extension popularized the "context window bar" UX — a compact horizontal bar showing per-category token usage with color coding. We adopt the same pattern but integrate it into dscode's warm flat-design aesthetic and the existing WebSocket event model.
8+
9+
**Constraints:**
10+
- Must work within the existing warm design system (no new design language)
11+
- Must not add external dependencies
12+
- Must reuse existing `estimateTokens` infrastructure
13+
- Must handle both light and dark themes via CSS custom properties
14+
- Bar must fit comfortably in the header center without crowding other elements
15+
16+
## Goals / Non-Goals
17+
18+
**Goals:**
19+
- Show a real-time, segmented horizontal bar in the WebUI header center representing context window usage
20+
- Color-code segments by content category: system prompt, user messages, thinking, tool calls (file read, file edit, terminal, browser), and free space
21+
- Display a compact numerical summary (e.g. `8.2k / 128k tokens`)
22+
- Update the bar after each turn completes and periodically during long streaming turns
23+
- Use the warm design system's existing CSS custom property infrastructure
24+
25+
**Non-Goals:**
26+
- Adding context window usage to the TUI (this is WebUI-only)
27+
- Historical context window charts or analytics
28+
- Customizable category colors via settings
29+
- Per-session context window history
30+
- Compaction trigger recommendations or warnings (just visualization)
31+
32+
## Decisions
33+
34+
### 1. Per-category token estimation lives in `ContextManager`
35+
36+
**Decision:** Extend `ContextManager` with a `getCategoryBreakdown(messages, tools)` method that returns estimates per category. The method iterates raw messages plus the current turn's tool calls and classifies each into a category enum.
37+
38+
**Rationale:** Token estimation logic already lives here. Keeping it centralized avoids duplicating the CJK-aware estimation formula. The method accepts tool calls as a separate parameter because tool calls are tracked in `WebUiBackend.currentAssistant.tools`, not in the raw message array.
39+
40+
**Alternatives considered:**
41+
- Putting estimation in `WebUiBackend`: Rejected — would duplicate estimator logic and create a second source of truth.
42+
- Putting estimation in the frontend: Rejected — the frontend doesn't have raw message content after server-side formatting, and estimating on the frontend would be inaccurate for CJK text.
43+
44+
### 2. New `context_window` ServerEvent
45+
46+
**Decision:** Add a new `context_window` event to the `ServerEvent` union:
47+
48+
```typescript
49+
{ type: "context_window"; total: number; used: number; free: number;
50+
categories: { system: number; user: number; thinking: number;
51+
fileRead: number; fileEdit: number; terminal: number;
52+
browser: number; other: number } }
53+
```
54+
55+
**Rationale:** A dedicated event type is cleaner than piggybacking on `ready` or `sessions`. It's sent independently and can be throttled without affecting other event streams. The category breakdown uses fixed keys so the frontend can reference them directly without string parsing.
56+
57+
**Alternatives considered:**
58+
- Piggyback on `sessions` event: Rejected — `sessions` is already data-heavy and sent for session list changes, not context changes.
59+
- Piggyback on `assistant_end`: Rejected — we also want updates during long turns (at each tool_end).
60+
- Generic payload like `Record<string, number>`: Rejected — fixed keys give TypeScript type safety.
61+
62+
### 3. Broadcast timing: throttled, not on every event
63+
64+
**Decision:** The backend broadcasts `context_window` on these triggers, throttled to at most once per 500ms:
65+
- On `ready` (initial connect / re-sync)
66+
- After each `tool_end` (tool call accumulated), throttled
67+
- After `assistant_end` (turn complete), always sent
68+
- After `clear_conversation`, always sent
69+
70+
**Rationale:** Sending on every `text_delta` would flood the WS connection (streaming can produce hundreds of deltas per second). 500ms throttle during streaming + always-on-turn-end ensures the bar is responsive but not wasteful. The bar's visual update is smooth enough at 2 FPS.
71+
72+
### 4. Frontend component: `ContextWindowBar`
73+
74+
**Decision:** A standalone React component that receives the `context_window` event data as props and renders a horizontal segmented bar. The component:
75+
- Stores the latest `ContextWindowData` in state (set via `App.tsx` event handler)
76+
- Renders nothing (returns null) when no data is available yet
77+
- Renders the bar using inline `style` props referencing CSS custom properties (matching the existing warm design pattern)
78+
- Shows a tooltip on hover with the full per-category breakdown
79+
80+
**Rationale:** Keeping it as a single component with a focused concern (context visualization) follows the project's "one file, one responsibility" rule. Using inline styles with CSS custom properties maintains consistency with the rest of the codebase.
81+
82+
### 5. Color palette for categories
83+
84+
**Decision:**
85+
| Category | Light theme | Dark theme | Meaning |
86+
|----------|------------|------------|---------|
87+
| system | `#8a8580` | `#8a8580` | var(--color-text-muted) |
88+
| user | `#ca8a04` | `#d49708` | var(--color-accent) |
89+
| thinking | `#a78bfa` | `#7c6bb0` | muted violet |
90+
| fileRead | `#eab308` | `#ca8a04` | amber-yellow |
91+
| fileEdit | `#3b82f6` | `#60a5fa` | blue |
92+
| terminal | `#ef4444` | `#f87171` | red |
93+
| browser | `#8b5cf6` | `#a78bfa` | purple |
94+
| other | `#6b7280` | `#9ca3af` | gray |
95+
| free | transparent | transparent | background shows through |
96+
97+
Defined as new CSS custom properties on `:root` and `.dark`: `--cw-system`, `--cw-user`, etc. This maintains the warm design system pattern and ensures theme transitions work automatically.
98+
99+
### 6. Header layout: flexbox with three zones
100+
101+
**Decision:** Modify the `<header>` in `App.tsx` from a two-zone layout (justify-between left/right) to a three-zone layout: left (branding + model + sidebar toggle), center (ContextWindowBar), right (theme + connection status). The center zone uses `flex: 1` with `justify-content: center` so the bar is naturally centered regardless of left/right content width. On small screens (`<768px`), the bar hides entirely (too narrow to be useful).
102+
103+
## Risks / Trade-offs
104+
105+
- **Token estimation is approximate**: `estimateTokens()` uses character-count heuristics, not actual tokenization. → Mitigation: Bar display labels include a tooltip noting "Estimated" and the numbers are good enough for budget awareness.
106+
- **Bar too wide on narrow viewports**: The header already has several elements. → Mitigation: Hide bar below 768px (matching the existing `md:` breakpoint pattern). The bar only needs ~240px minimum.
107+
- **Category classification is heuristic**: We classify tool calls by name prefix (`read_file`, `write_file`, `bash`, `browser_*`). Future tool names might not match. → Mitigation: Fall back to `other` category for unrecognized tools; easy to extend the pattern matcher.
108+
- **Extra WS traffic**: Each `context_window` event adds ~150 bytes. → Mitigation: Throttled to 500ms during streaming (~300 bytes/sec worst case), negligible vs. the existing delta stream.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
## Why
2+
3+
Users currently have zero visibility into how much of the model's context window is consumed by message history (system prompt, user messages, tool calls, etc.). This leads to blind spots: users don't know when they're approaching the context limit, which tool types dominate usage, or why the agent starts forgetting earlier context. In the Cline/Roo Code ecosystem, the context window bar is a beloved UX pattern that builds user trust by making the token budget transparent. We should bring the same clarity to dscode's WebUI.
4+
5+
## What Changes
6+
7+
- Add a real-time **context window usage bar** in the center area of the WebUI title bar header
8+
- The bar visualizes the context window as a horizontal segmented bar with per-category color coding (system prompt, user messages, file reads, file edits, terminal commands, browser use, free space)
9+
- A new `context_window` server-to-client WS event pushes token breakdown data to the frontend
10+
- The `ContextManager` is extended to track and expose per-category token estimates
11+
- The bar updates live as messages and tool calls accumulate during a session
12+
13+
## Capabilities
14+
15+
### New Capabilities
16+
17+
- `context-window-bar`: A real-time horizontal segmented bar in the WebUI header showing context window usage broken down by message/tool category with per-category colors, free space indicator, and numerical token count display.
18+
19+
### Modified Capabilities
20+
21+
- `websocket-protocol`: New `context_window` server event type added to `ServerEvent` union for pushing token breakdown data.
22+
- `web-frontend`: Header layout modified to render the context window bar in the center region; new requirement for the bar component's styling and behavior.
23+
24+
## Impact
25+
26+
- **Backend**: `ContextManager` in `src/context/manager.ts` — add per-category estimation; `WebUiBackend` in `src/ui/web/web-backend.ts` — broadcast `context_window` events on message/tool activity
27+
- **Shared types**: `ServerEvent` union in `src/ui/shared/types.ts` — new `context_window` event type
28+
- **Frontend**: New `ContextWindowBar` component in `web/src/components/`; `App.tsx` header layout — render the bar in center
29+
- **Design tokens**: New CSS custom properties for category bar colors in `web/src/index.css`
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Context window usage bar in header
4+
The WebUI SHALL render a segmented horizontal bar in the header center area that visualizes context window token usage, broken down by content category.
5+
6+
#### Scenario: Bar renders when data is available
7+
- **WHEN** the frontend receives a `context_window` event from the server
8+
- **THEN** the ContextWindowBar component SHALL render a horizontal bar in the header center showing colored segments for each category proportional to their token count
9+
- **AND** the free space SHALL appear as a transparent/background-colored segment filling the remaining bar width
10+
11+
#### Scenario: Bar is hidden when no data
12+
- **WHEN** no `context_window` event has been received yet (initial load)
13+
- **THEN** the ContextWindowBar component SHALL return null and render nothing
14+
15+
#### Scenario: Bar hidden on narrow viewports
16+
- **WHEN** the viewport width is less than 768px
17+
- **THEN** the ContextWindowBar SHALL be hidden (display: none or conditional render)
18+
19+
### Requirement: Category color coding
20+
The ContextWindowBar SHALL use distinct colors for each content category, defined as CSS custom properties for light/dark theme support.
21+
22+
#### Scenario: Colors defined as CSS custom properties
23+
- **WHEN** the stylesheet is loaded
24+
- **THEN** `:root` SHALL define `--cw-system`, `--cw-user`, `--cw-thinking`, `--cw-file-read`, `--cw-file-edit`, `--cw-terminal`, `--cw-browser`, `--cw-other` custom properties
25+
- **AND** `.dark` SHALL define corresponding dark variants of each property
26+
27+
#### Scenario: Each category uses its designated color
28+
- **WHEN** the ContextWindowBar renders a segment for category "user"
29+
- **THEN** the segment's background-color SHALL be `var(--cw-user)`
30+
31+
### Requirement: Numerical token summary
32+
The ContextWindowBar SHALL display a compact numerical summary of token usage adjacent to or overlaid on the bar.
33+
34+
#### Scenario: Summary shows used/total
35+
- **WHEN** the ContextWindowBar renders with data `{ used: 8200, total: 128000 }`
36+
- **THEN** the component SHALL display "8.2k / 128k" (or equivalent human-readable format) as a text label
37+
38+
#### Scenario: Summary updates on new data
39+
- **WHEN** a new `context_window` event arrives with updated token counts
40+
- **THEN** the displayed summary SHALL update to reflect the new values
41+
42+
### Requirement: Hover tooltip with detailed breakdown
43+
The ContextWindowBar SHALL show a tooltip on hover revealing the per-category token counts with human-readable category names.
44+
45+
#### Scenario: Tooltip appears on hover
46+
- **WHEN** the user hovers over the ContextWindowBar
47+
- **THEN** a tooltip SHALL appear listing each category with its name, token count, and color indicator
48+
49+
#### Scenario: Tooltip hidden on mouse leave
50+
- **WHEN** the user moves the mouse away from the ContextWindowBar
51+
- **THEN** the tooltip SHALL disappear
52+
53+
### Requirement: Bar updates in real-time
54+
The ContextWindowBar SHALL update its display each time a new `context_window` event is received from the WebSocket connection.
55+
56+
#### Scenario: Bar updates after tool call
57+
- **WHEN** a tool call completes and the server sends an updated `context_window` event
58+
- **THEN** the bar's segments and numerical summary SHALL re-render to reflect the new token counts
59+
60+
#### Scenario: Bar updates after turn completes
61+
- **WHEN** the assistant turn ends and the server sends an updated `context_window` event
62+
- **THEN** the bar's segments and summary SHALL re-render
63+
64+
### Requirement: Segments rendered with minimum visible width
65+
The ContextWindowBar SHALL ensure segments that represent a non-zero but very small percentage of the context window are still visible.
66+
67+
#### Scenario: Small segment gets minimum width
68+
- **WHEN** a category has 0.1% of total tokens (nearly invisible at full scale)
69+
- **THEN** that segment SHALL render with a minimum width of 2px so it remains perceptible
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## MODIFIED Requirements
2+
3+
### Requirement: Conversation view
4+
The WebUI SHALL render a `ContextWindowBar` component in the center zone of the `<header>` element, between the left branding/model area and the right theme/connection area. The header SHALL use a three-zone flexbox layout with the center zone growing to fill available space. On viewports narrower than 768px, the ContextWindowBar SHALL be hidden.
5+
6+
#### Scenario: Header renders three zones
7+
- **WHEN** the WebUI is loaded on a viewport >= 768px wide and context window data is available
8+
- **THEN** the header SHALL show: left zone (sidebar toggle + DSCode branding + model name), center zone (ContextWindowBar), right zone (theme toggle + connection status)
9+
10+
#### Scenario: Center zone is centered
11+
- **WHEN** the header renders with all three zones
12+
- **THEN** the center zone SHALL use `flex: 1` and `justify-content: center` so the ContextWindowBar is horizontally centered regardless of left/right content widths
13+
14+
#### Scenario: ContextWindowBar hidden on mobile
15+
- **WHEN** the viewport width is less than 768px
16+
- **THEN** the ContextWindowBar SHALL not be rendered
17+
- **AND** the existing two-zone (left/right) layout SHALL be preserved
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Context window event
4+
The WebSocket protocol SHALL include a `context_window` server-to-client event type that carries token usage breakdown data.
5+
6+
#### Scenario: Server sends context_window after turn end
7+
- **WHEN** the agent completes a turn (`assistant_end`)
8+
- **THEN** the server SHALL broadcast a `context_window` event with the updated token breakdown
9+
10+
#### Scenario: Server sends context_window on connect
11+
- **WHEN** a WebSocket connection is established and the `ready` event is sent
12+
- **THEN** the server SHALL also send a `context_window` event with the current token breakdown
13+
14+
#### Scenario: Server sends context_window after clear
15+
- **WHEN** the conversation is cleared via `/reset`
16+
- **THEN** the server SHALL broadcast a `context_window` event with used=0
17+
18+
#### Scenario: Context window event payload structure
19+
- **WHEN** the server sends a `context_window` event
20+
- **THEN** the payload SHALL include: `type: "context_window"`, `total: number` (context window size), `used: number` (total tokens used), `free: number` (available tokens), `categories: { system: number, user: number, thinking: number, fileRead: number, fileEdit: number, terminal: number, browser: number, other: number }`
21+
22+
### Requirement: Context window event throttling
23+
The server SHALL throttle `context_window` event broadcasts to at most once per 500ms during active streaming (between `assistant_start` and `assistant_end`). The event at `assistant_end` SHALL always be sent regardless of throttle window.
24+
25+
#### Scenario: Throttling during tool calls
26+
- **WHEN** multiple tool calls complete within a 500ms window during a streaming turn
27+
- **THEN** only the first `context_window` event is sent immediately; subsequent ones are coalesced and sent at most 500ms after the last one
28+
29+
#### Scenario: No throttle at turn end
30+
- **WHEN** `assistant_end` fires and the last `context_window` was sent 100ms ago
31+
- **THEN** the `context_window` event SHALL still be sent immediately (throttle bypassed at turn end)

0 commit comments

Comments
 (0)