The Context Stats card's inject/compaction/prune cells duplicated what the
events card already tracks. Drop the three cells (the grid falls to five:
turns, steps, tool calls, cache hit, cost) and show each tally inside the
matching kind-filter button instead, sourced from the same precomputed
counts the stats card used.
The rates themselves check out against the official pricing pages (both
currencies, both families, and the Beijing peak windows), but the flash
row's label named deepseek-v4.1-flash — the model VERSION, not an API ID.
The page's current flash ID is deepseek-flash, with deepseek-v4-flash as
the accepted legacy ID, so the label and the tip's applies-to list now
cite those; the tip also drops the colloquial hardcoded phrasing for a
plain for-reference-only note.
The images cell said little about the session; the eighth cell now shows
the whole-session prompt-side cache-hit rate off the official tokenUsage
projection — the same source and formula as the chat stats line under the
composer, truncated to one decimal (cacheHitPercent gains a precision
parameter; the two-decimal default keeps the request detail's figure).
The donut center now carries the whole-session billed total — the same
figure the harness chat stats line shows under the composer (uncached
input + cache read + cache write + output off the official tokenUsage
projection) — instead of the cache-hit share.
The slices answer WHAT the tokens are rather than how the provider
cached them: the six composition categories share the provider-reported
prompt-side total by the composition card's own estimated ratios (their
counts carry the ≈ marker), and the provider-exact output closes the
ring, so the parts equal the chat line's figure by construction.
The cache-read/cache-write/uncached bucket view and its four locale
keys are gone; tokens.total is new.
A turn aggregate IS its last step's record (the same seq), so with a
plain seq key a step ↔ turn switch reused those bars' DOM nodes — the
entrance animation had already finished on them and CSS never replays an
animation on a reused node, leaving every turn's last step bar popping
in unanimated. Keying ChartBar by seq + granularity forces a remount;
mode (Total/Delta) switches already remount through the stack ↔ arms
structural change.
The donut slices sweep in clockwise from 12 o'clock, the composition
stacked bar grows open segment by segment, and the trend chart bars rise
from their baseline — all staggered in layout order, transform-only (no
layout thrash), running once per mount and disabled under
prefers-reduced-motion (the harness's own idiom).
- the stagger caps (trend bars 20, stacked pieces 8) so long logs and
long DNA band lists still settle in well under a second
- the auto-compaction reserve band stays unanimated: as an overlay its
growth would smear the stripes, and it reads as chrome, not data
- covers the tab, the /context modal and the right Sidebar, which all
render the same chart components
DeepSeek has ended the routing window: deepseek-v4-pro (DeepSeek-V4-Pro-0813)
now bills at its own published table instead of riding the V4.1 Flash triple —
USD peak hit $0.044 / miss $1.32 / out $3.96, CNY peak ¥0.3 / ¥9 / ¥27,
off-peak half price. Flash rates are unchanged.
Clicking a file name in the File Activity card now opens the file's preview
tab in the right Sidebar — the shipped document-preview viewer, reached the
same way the built-in Files sidebar reaches it — on harnesses that serve that
column (dsh 0.1.5-rc.1+). The address is built with the harness's own
browser-safe `fileAddressFor`, inlined by the client bundle exactly as the
Sidebar's file types inline it, so a path under the workspace collapses to the
session-relative spelling and every route to one file settles on one tab.
The capability is OPTIONAL by contract, like the tab registration itself: the
opener reads `ctx.sidebarRight.openResource` through the runtime boundary and
re-proves it per call, and reports whether the column took the address. An
absent column, a face that refuses the address, or an unencodable log path
falls through to the pre-existing system-open behavior, so an older line is
fully functional and nothing becomes an inert click. Pattern rows and
directory targets are never previewable.
Adds the `@deepseek-ai/dsh-util-workspace-path` dev dependency (itself
browser-safe), allows it in the client bundle purity gate, pins the
resource-navigation seam in the compat matrix, and covers the address builder,
the card's preview-then-fallback click, and the optional opener per baseline.
The baseline gate resolved the harness version from the healed
`$DSH_HOME/profiles/node_modules` mirror first, but that mirror is only as
fresh as the last CLI profile boot: it is rewritten by whichever installation
last healed it, and a packaged desktop host never heals it at all. A leftover
global CLI could therefore pin the mirror below the baseline and gate a
healthy desktop harness — an upgrade modal plus fallback (unfolded) projection
units on every launch (issue #59).
Resolve through the module tree the plugin is actually bound to instead: its
own ESM resolution (`import.meta.resolve`, the same pipeline its runtime
imports use, so an embedding shell that redirects plugin imports — the
packaged desktop's ASAR resolver bridge — redirects the probe too) of the
library packages it imports. A witness that lands inside this package's own
tree is the plugin's dependency closure (a `link:`-installed dev checkout's
pinned devDependencies), never the harness, so it is discarded. The home
mirror stays as the fallback for a dev checkout and for a running anchor that
cannot answer; the CLI package is still probed only through it.
Fail-open is unchanged: any unresolvable package, non-file URL, or unreadable
manifest degrades to `undefined`, which never trips the gate.
A config problem must never leave a Context entry hidden:
- a scope value the plugin cannot understand (present but invalid)
degrades the placement to `all` instead of stranding the previously
chosen one — the read path now distinguishes invalid from missing,
and only a genuinely absent field (older Host half) keeps the
current state;
- a placement write that fails to persist no longer keeps the
optimistic echo in-session: unless the scope's truth itself carries
a valid placement, the rollback degrades to `all` (other fields
keep the existing keep-the-echo behavior).
Closes#58: the Context panel hung off two registrations at once (the
conversation tab and the right-Sidebar guide entry), with no way to keep
just one. A new per-user `defaultPlacement` preference — the first row of
the Plugin configuration card — picks who carries the view: the
conversation tab, the right Sidebar, or both (the default, so existing
deployments see no change).
- shared: `DefaultPlacement` vocabulary (`all` | `tab` | `sidebar`) on the
settings namespace; the Host schema gains a loose `defaultPlacement`
field so a stale persisted value degrades to `all`
- client: `placement.ts` mounts each registration through its own factory
and, on a preference flip, disposes exactly what the new value drops
(the harness's own dynamic-mount idiom); `watchSidebarContextTab`
returns its deferred-inject disposer so the sidebar mount can be taken
down and put back; the settings card leads with the placement row
A ring with a neighbour on each side now gives half a 0.5-unit gap off
both ends of every arc (1 unit ≈ 1% of the circumference, so ~1px on the
96px card ring), letting the card's own background draw the line between
neighbouring colours. A lone painted slice keeps its full ring — a gap
there reads as a nick — and the cut never takes more than a quarter per
end, so a sliver like the 0.2% overhead wedge never vanishes whole.
Bundle the colourful document sheet as the plugin's own icon and fill the
right-Sidebar tab type's two glyph seats with it, the way the shipped files
type does: the guide capsule's `icon`, and a new `sidebar.right.pane.tab.title`
registration that draws the emblem beside the chip label.
The chip's close control sits out of flow over the title's last 14px and an
active chip fades the 16px before it, so the shipped files chip stays readable
only by its short label. The label now carries a trailing gutter so that fade
lands past the text instead of on the last glyphs.
Registration turns rollback-safe (each disposer is tracked) so a partial
failure unwinds cleanly, and the obsolete primitives-icon resilience spec goes
with the icon read it guarded.
`SidebarRightGuideEntry` carries `order`, `title`, and an OPTIONAL
`description` again at dsh 0.1.5-rc.1 — it was required at 0.1.5-alpha.1,
dropped at 0.1.5-alpha.2, and restored optional now. The plugin's guide
capsule re-adds its `description` thunk and the `sidebar.guideDescription`
dictionary keys, so the right Sidebar's guide entry renders the one-line
subtitle the shipped Files type does.
The mirrored face marks the thunk optional: a line whose guide body ignores
it simply goes without, so the same contribution stays valid across the
whole 0.1.5 line.
The supported V3 line follows the newest release: the baseline becomes
v0.1.5-rc.1 (tag, vendored session package) and the declared release map
names 0.1.5-rc.1. The release workflow fetches the new tag, and the
disposable-profile install/uninstall was verified for real against the
published 0.1.5-rc.1 CLI (install OK -> 1 composed row -> uninstall OK ->
0 rows).
At 0.1.5-rc.1 `SidebarRightGuideEntry` regained its optional `description`
line, so the guideEntry probe pins order/title/description/icon and the
now-pointless "absent" assertion plumbing is dropped.
Every support/capability label states the 0.1.5-rc.1+ floor;
`0.1.5-alpha.x+` remains only as the V3 LOG-generation label (the format
first appeared at 0.1.5-alpha.1). The docs record the verified matrix and
the seam movement across alpha.1/alpha.2/rc.1.
The family matcher required a `v4` marker, so `deepseek-flash` requests
went unpriced. Accept `deepseek` as an alternative marker — proxy
spellings like `deepseek/deepseek-flash` land too, while foreign
flash-named models (gemini-2.0-flash) still match no family. The stats
board's price-list label and both locales' cost tooltip name the new
model beside deepseek-v4.1-flash.
Every rich detail section (system prompt, tool schema descriptions,
messages, tool results, compaction summaries, and the same cards inside
the /context modal) gains an icon-only copy control beside the Raw/MD
switch. It copies the section's exact SOURCE text, never the rendered
markdown, via the harness's own writeClipboard primitive; success flips
the glyph to a check for a beat, a rejected write claims no success, and
a repeat click inside the window is a no-op — the shipped code-block
control's behavior. Both icons and the writer are platform-module
exports present on all three supported baselines.
The added control tipped the head's trailing group out of the clipped
section card at the narrowest pane, so rich-text heads now fold that
group onto a second line under width pressure. The wrap is scoped to
those heads: the call-name head must stay one line (long MCP names
ellipsize instead).
A slice that never happened still rendered as a row — "0.0%" plus a count
borrowed from elsewhere on the card — which reads as "this ran 323 times in
no time". The ring has always skipped zero arcs; now the legend matches it,
so the card shrinks to the slices that actually carry time.
Zero slices are common: across 307 local sessions, reasoning is zero in
23.5%, answer text in 14.3%, tool args in 13.7%, and tools in 14.3% (a
one-turn "hi" session collapses from six rows to two). Overhead is the
residual and effectively never zero, so the change bites the decode and
tool rows. Every slice is a share of the wall total, so dropping rows never
distorts the remaining percentages.
The Timing card's model slice expands into Thinking / Answer / Tool args
whenever the host served the split; an older cached row (or a log whose
stream carried no block markers) keeps the single LLM Gen row, so nothing
degrades. The ring shares the generation window across the buckets in card
order, clamped exactly as tools already were.
The decode rows carry no call-count qualifier: they count decode BLOCKS,
and one model call emits zero to many of them (a single call routinely
requests several tools; most calls emit no reasoning at all), so the
session's call count was a false per-slice tally. Measured on a real
236-call session: 236 calls produced 302 tool-call blocks, and 1078 of
3037 calls emitted no reasoning. TTFT and tools keep their counts — those
happen exactly once per call/run.
`timingOf` narrows the new optional scalars without materializing absent
ones, and the fast path applies the same non-negative gate as the slow
path so a hostile bucket cannot slip through.
The timing totals priced a model call as TTFT + one opaque generation
window, so thinking, answer text, and tool-argument decoding were
indistinguishable. Fold the stream's `block-start` framing into three new
additive-optional buckets — `reasoningMs`, `textMs`, `toolArgMs` — that
tile the window's marker span.
Both log generations feed the same split: a V0 log's `assistant/chunk`
markers open and close spans in the step slot; a V2+ log carries no chunk
events, so `decodeSpansOfStream` reads them off the embedded settlement
stream. The split is priced only when the window was (an unstamped call's
model time stays unattributed wholesale), and a zero span stays absent so
pre-split-shaped state gains no dead properties.
The fields are additive-optional, so `stateVersion` stays at 15 and
cached rows keep parsing — a bump would discard them and orphan the key
for idle sessions (the #37 regression).
Measured over 305 local sessions: reasoning 31.3% of active time, tool
args 15.3%, answer text 3.5% — the answer is 1/9th of what the model
spends decoding.
The right Sidebar's Context panel split its three main cards into three
columns, so a pane too narrow for three folded into a ragged 2+1: a hole
under the composition card and the trend card alone on its own row. The
conversation tab read differently, from the same component.
Drop the per-host arrangement: both hosts now use one two-column row
(composition over trend in the left column, the browser beside it and
stretched to the pair's height). The panel's row simply folds to a single
column at the shared 360px floor instead of splitting across two rows.
The head row is untouched, so the panel still shows its two donut cards.
DeepSeek reprices the Flash series effective 2026-09-10 12:00 BJT
(off-peak per 1M tokens: hit ¥0.02/$0.003, miss ¥1/$0.15, out ¥4/$0.6;
peak at 2x). Between the V4.1 Flash launch and the V4.1 Pro release
every Pro request is routed to V4.1 Flash and billed at the Flash
rates, so pro shares the flash triple until V4.1 Pro is priced — swap
that one reference when it ships. The stats-board tooltip names the
flash row deepseek-v4.1-flash, and both locales note the interim
routing. Fold logic is untouched: the substring family match already
lands v4.1 spellings (new fixture), and the peak windows are
unchanged. Logs spanning the cutover estimate entirely at the new
table — the cell is a reference estimate by contract.
SidebarRightGuideEntry now carries only `order`, `title`, and `icon`,
and the guide page renders a glyph + title capsule, so the plugin's
contribution drops its description thunk and the now-unused
`sidebar.guideDescription` dictionary keys. The mirrored face and the
sidebar's version labels state the supported 0.1.5-alpha.2+ floor.
The supported V3 line follows the newest release: the baseline becomes
v0.1.5-alpha.2 (tag, vendored session package) and the declared release
map names 0.1.5-alpha.2. The release workflow fetches the new tag.
The alpha.2 client seams are pinned in the matrix: the conversation
surface moved under the keyed `main` panel (`main.conversation`) while
the plugin's `conversation.view` seat keeps its name, and
`SidebarRightGuideEntry` dropped its description line. tests/baselines.ts
therefore gains a guideEntry probe (the fields present plus the dropped
copy field asserted absent).
Every support/capability label states the 0.1.5-alpha.2+ floor;
`0.1.5-alpha.x+` remains only as the V3 LOG-generation label. The docs
record the verified matrix, the two moved seams, and the install row
that still waits on the CLI's npm publication (the `alpha` dist-tag
lags the release).
Each line carries its own durable-event vocabulary (assistant/chunk and tool/code-dispatch are V0/V2-only; system/message and tool/ptc-dispatch are V3-only), so Baseline.foldEventTypes replaces the single global list and a matrix test asserts the union still covers every fold case. The release workflow fetches both new tags before pnpm test.
The supported range (dsh 0.1.2-rc.1+) spans three durable-log generations, and the fold only read the V0 spellings — so on 0.1.5-alpha.1 sessions the system prompt priced 0, TTFT/generation stayed 0, and startSeq/endSeq replacements appended instead of replacing.
Read SHAPES, never a detected version (the probe can be wrong: a healed profile mirror may name another release than the running harness):
- system/message surface nodes feed systemTokens plus a bounded systems list (the harness's "last nonempty surviving system" rule), replacing the V0/V2 request/header.header.system envelope figure; the browser resolves the prompt in force per step and fetches its text from either event shape.
- The first token comes from the embedded assistant stream (assistant/message.data.stream / assistant/attempt.data.stream) when the log carries no V0 assistant/chunk deltas, mirroring dsh-llm's assistantStreamFirstTokenTime.
- Replacement endpoints accept both startSeq/endSeq and start/end.
- tool/ptc-dispatch books nested file ops alongside tool/code-dispatch.
Statistics were differentially verified against the harness's OWN projection values on real V0, V3-native, and V3-migrated logs: system/tools/message tokens, request and turn counts, TTFT, generation, tool time, and every billed cost bucket match exactly.
The new state fields are additive-optional, so stateVersion stays 15: a bump would orphan the key for idle sessions (the #37 regression).
The hover-underline/click jump the plugin wired onto the harness chat
stats row conflicts with the reworked stats-line detail view shipping
in dsh 0.1.5. Drop the composer-dock entry, its styles, i18n keys, and
tests wholesale - the stats line renders untouched again.
Closes#53.
A trackpad swipe past either edge of the trend chart's horizontal scroller
chained into the page and the browser read it as its history gesture, so a
few scrolls in the Context tab could leave the whole app for another page in
the tab's history (back at the left edge, forward at the right).
Two layers, matching what the harness already does for its own horizontal
scrollers: `overscroll-behavior-x: contain` on the chart scroller and the
agent graph's stage stops the chaining (Chromium/Firefox), and overscroll.ts
cancels the horizontal-dominant wheel the scroller cannot consume at either
boundary for WebKit, which still navigates on horizontal overscroll with the
property set (WebKit bug 240183). Vertical-dominant gestures are untouched,
so a wheel over the chart still scrolls the page.
A title-row Adaptive switch redraws the chart against the bars currently
visible in the scroller instead of the whole retained log: the axis follows
the scroll and pane resizes, so the first step's system prompt + tool schemas
(a spike far off screen) no longer flattens every bar the reader is looking
at. Per-bar totals scale total mode, the diverging arms scale delta mode, and
the window reads the exact 14px column + 2px gap grid, so a column parked in
the gap never joins it.
The switch reuses the Context Browser's DNA-toggle chrome and stays
mount-local (no settings write); off keeps the whole-log scale. A window
carrying no figure of its own — an empty history, a zero-width pane, or an
all-zero delta window — falls back to that scale, and unchanged maxima
re-render nothing.
The "✂ marks compaction/prune, Step/Turn switches granularity" caption
restated controls that are already visible in the card's own title row,
so it only crowded the header. The subtitle span now renders solely for
the transient focus state ("Showing {cat} only"), and the unused
trend.hint key leaves both dictionaries.
A snapshot push can drop the band under a resting pointer (compaction,
tail slide out of the served window) without a mouseleave, and the dead
key exact-matched nothing: the whole strip sat dimmed with nothing lit
— the very state the open-category pin's breakdown gate rules out.
Honor a band hover only while its key names a rendered band, so the
mirror/pin fallbacks resume underneath.
Also correct the hover-link comment: DNA hovers stay on the bar and
never report back to the overview (the link is one-way in DNA mode,
as the existing test already pins).
The browser's accordion already focuses the Context trend chart on the
open category; the composition bar now holds the same highlight as a
resting state — category mode lights the category's segment, DNA mode
lights the category's bands as a group (the parts' group key). A
pointer hover overrides the pin and the pin resumes on leave; the pin
drops while a previewed/picked step holds nothing for the category, so
the bar never reads all-dimmed with nothing lit. No tooltip floats for
the pin: category mode mounts no tip slot, and the group-keyed pin
exact-matches no DNA band.
A title-row toggle redraws the composition bar as one band per context
item — the system prompt, every tool schema, every surface message — in
prompt order (the header prefix first, then the message flow by seq),
the order the model reads the context and the order items joined it.
The header epoch's own seq is bookkeeping (dsh logs request/header at
dispatch of the first request using it), never an ordering key: a
mid-session refresh must not park the schemas behind the tool results
they describe.
Bands floor at 0.35% of the occupied region so a tiny item stays a
hoverable filament (tooltips still report true shares); hovering names
the item with its time, a mirrored category hover lights the category's
bands as a group, and clicking a band opens its category + element row
below, scrolling it into view.
The trend bars multiplied every category by a per-request anchor factor
(provider prompt / heuristic total). That factor drifts with the heuristic's
ratio error as the context composition changes, so categories that never
change — the system prompt, tool schemas — plotted as steadily growing
(1668→1789 on a real log whose fold value is a constant 1851), and the delta
mode invented non-zero changes for them. The same screen already shows the
raw figures everywhere else (step detail, browser, overview), and dsh's own
token-meter breakdown projection serves un-rescaled heuristics per request,
so drop the anchor: segments, axis, tooltip and the focused view all plot the
fold's figures directly. Provider usage stays visible in the step detail's
Actual Prompt / Output / Cache chips.
A mount could race the declared inject (a watch rebuild remounts the
Context tab and the /context modal before the injected fiber re-fires),
and the fetchers snapshot the absent face once — the browser then stuck
on the static degradation for its whole lifetime, with no details
renderable in any category. The face now lives behind a
subscribe+useSyncExternalStore seat (useHistoryFace), and both
components rebuild their fetchers when it lands or is revoked, so an
open section auto-fetches without any user action. The /context modal
also gains the session image loader the tab already wires, so
attachment cards stop degrading to metadata-only there.
A durable `user/message` event lacking a non-empty string `data.id` passes
the runtime append unchecked but makes the harness refuse the whole session
at its next load (`... lacks an identified message`, issue #51). The archived
wrap-up notice carried a rebuild signature (role-first key order, `id` and
`summary` stripped) that no shipped harness producer emits, so the strip
happens upstream of the append, outside both audited codebases.
Guard the durability boundary from the plugin side: a prepended
`agent/pre-step` listener runs last in the waterfall and mints a `dshctx-`
prefixed id for any decision message that would persist unidentified,
leaving everything else — including the decision's reference identity —
verbatim, and failing open on hostile entries. The seam is pinned per
baseline (the event dispatch, the user/message append that consumes it,
and the prepend option) in the real-code matrix.
Expanding a category in the Context browser now plots every trend bar on
that category alone: the axis rescales to the category's own max (provider
anchor preserved) in both total and delta modes, the hover tip names the
category, and the card subtitle reads "Showing {cat} only" until the
category collapses. The /context modal keeps its unfocused chart.
The strip label is now the bare turn number (the block's hover title
keeps "T{n}"). Without the prefix, one/two-digit turns fit the 10px
base font at the fixed 16px column pitch with no shrink; the uniform
shrink now kicks in from three-digit turns (7px) and floors at 6px for
four-digit ones.