The composer ring, percentage, and `~used / capacity` header read
`contextPressure.pressureTokens`, which moves only when a request reports
usage. Compaction reports none — compact-basic summarizes through a direct
`ctx.llm.stream()` call and appends only its own `compact/*` records plus the
replacement `user/message` — so the meter was frozen across the one action
taken to change it. Driving a real `compactNow` through the agent loop:
BEFORE compact: ring=4% header=~4227/100000 rows=[18, 0, 4365]
AFTER compact: ring=4% header=~4227/100000 rows=[18, 0, 286]
The composition rows fell 93%; the ring did not move, and would not until an
entire further turn completed. The panel then contradicted itself by more than
an order of magnitude at exactly the moment a reader opens it.
`contextPressure` now also publishes `projectedTokens`: the provider sample
plus the heuristic repricing of everything the surface gained or lost since
that sample, clamped at zero, folded through the shared `surface-fold.ts`. The
sample is stamped before the same event joins the surface, so an
`assistant/message` anchors against the surface its own request carried. Only
the delta is estimated, so the figure stays provider-anchored — the estimator's
CJK and JSON-schema underpricing stays out of the occupancy number — while
reacting the moment content lands or a span is shadowed. Same run after:
BEFORE compact: ring=4% header=~4323/100000 (pressure=4227, projected=4323)
AFTER compact: ring=0% header=~ 244/100000 (pressure=4227, projected= 244)
`contextOccupancy` prefers the projected figure and falls back to the bare
sample, so a projection restored from a pre-field checkpoint degrades to the
old behavior rather than disappearing. `stateVersion` moves to 3.
The panel header concatenated a `45%` span with a `context.used` fragment, so
Chinese rendered "45% 上下文已用" against the ring's own "上下文已用 45%". The
header now renders the one localized `context.aria` sentence split around its
`{percent}` slot: each locale owns the reading's position while the reading
keeps its primary tone, and the side a locale leaves empty collapses through
`.headline:empty` instead of spending a header gap.
The bar mapped every composition row to a segment unconditionally, and
`.segment`'s 2px min-width kept each one visible, so a 0% occupancy panel
painted an ~8px filled bar over an empty context. Parts are now computed with
their widths and zero-width parts are filtered out, which also collapses the
plain and segmented branches into one map.
Assistant footers and the stats line gain TTFT/tok-per-second readings
folded from step timings; context occupancy moves off the stats line onto
a composer ring whose panel shows a heuristic system/tools/messages
breakdown from the new token-meter contextBreakdown session projection.
A completed pwsh foreground call now presents as the bash tool's terminal
card with the parsed exit-status pill instead of a generic console fence,
and the collapsed row classifies as the shell family (Pwsh title). The
marker-to-exit-status parse moves from dsh-tool-bash's private render
module into the @deepseek-ai/dsh-bash seam so both shell tools share one
inverse of the marker contract (the bash-env precedent). The Web UI needs
no per-tool code: the terminal-card bridge maps any card:'terminal' view.
Coverage: dsh-bash owns the parse edge cases; the tool-pwsh presenter
suite mirrors tool-bash's; the client row-model suite pins the Pwsh shell
row; the new keyless pwsh-terminal web lane seeds an authored session,
presents it through the real tool on replay, and pins the card golden.
TUI is out of scope: the TUI package was removed (ed30088adb), so the Web
surface is the only UI the gap affected; the roadmap proposal's stage 2
is updated accordingly.
Derive turn wall time from adjacent logged timestamps (no new session
events): the actions-owning assistant footer gains a localized
"Ran for {duration}" label, and the running TurnStatus label gains a
live elapsed clock anchored to the same logged trigger so a mid-turn
reload keeps the real elapsed time. Message time chrome is now
hover-revealed on hover-capable devices, keeping icons visible and
layout stable.
Steer, inject, and followup now land as durable user/message events on the
session surface; the steering/message event type and its ConversationNode
kind are removed from the client projection. Update tests, docs, generated
catalogs, and agent notes to match, and align the steering e2e fixture and
prompt inventory assertions with the durable user/message landing.
Replace the WebBlock search card's head/tail collapse and expand button
with a fixed-height scroll container that lists every source the tool
returned. The model-facing side is unchanged: the seam still caps sources
at searchMaxResults and the truncated indicator stays, so model-visible
and frontend-visible sources remain identical.
Remove CHAT_WEB_MAX_SOURCES and DEFAULT_WEB_MAX_SOURCES: with scroll, the
chat row and details panel show the same full list.