DSH profiles install out-of-tree plugins with `nodeLinker: hoisted` and
`autoInstallPeers: false` (packages/boot/app-boot/src/profile.ts), so a
peer-only zod was never materialized for the plugin: the host half's
`import { z } from "zod"` silently resolved to whichever zod the
profile's other packages happened to hoist. A profile whose hoisted zod
predates the Standard Schema `~standard` protocol (zod < 3.24) leaves
`Config["~standard"]` undefined and cordis's config validation throws a
bare TypeError, failing the whole plugin tree (#35).
Declare zod ^4.4.3 under dependencies so every install materializes a
standard-schema-capable copy (deduped with the host's own when
compatible), and under devDependencies so this workspace stops leaning
on a transitive devDep for it. The tsdown host build keeps every
production-section dependency external, so the bundle is unchanged.
`snapshotJsonValue` left @deepseek-ai/dsh-session in 0.1.2-alpha.2 (moved
to dsh-util-values). Inline the same validate-and-detach semantics in the
fold driver helper so the specs compile and run against every supported
dsh face.
MarkdownText on the 0.1.2-alpha line serves fence copy buttons and the
footnote heading through a required `labels` prop (renamed from the
optional `codeLabels`); rendering fenced code or footnotes without it
broke the browser's markdown sections. Pass a localized, reference-stable
chrome — pre-0.1.2 faces ignore the extra prop and render as before.
dsh 0.1.2-alpha.2 removed the `settingsNamespace()` export from
@deepseek-ai/dsh-settings, so the named import failed at plugin load time
on that host. Register the raw literal (valid under every version's
namespace pattern) and keep a branded cast for the pre-0.1.2 type face,
mirroring the migration dsh applied to its own packages.
The tip anchored to the chart's top INSIDE the plot — exactly where the tall
bars live — so it covered the hovered bar and its neighbours. It now
bottom-anchors 4px above the chart top (over the card header, clear of the
bars and the ✂ markers) while syncTip keeps gluing it horizontally to the
bar's visible slice. Pure CSS re-anchoring: no JS geometry change.
Turn-strip labels render at natural width, overflowing narrow blocks instead
of ellipsizing to "…" on two-digit turns; updateTurnLabels now also thins
colliding labels (one reaching the previously kept label's box drops to
visibility:hidden), so consecutive 14px turn bars stay readable while
scrolling.
The hover tip caps at the chart wrapper's width and wraps instead of hanging
past the card into neighbouring panes or off-screen, and is restructured into
two rows: the bar's identity and its anchor total — the SAME actual value the
bar height and the axis graduation scale against (provider-reported prompt
when available, heuristic total otherwise); delta mode keeps the signed net
row. Identity phrasing follows the granularity: a single-step turn aggregate
reads "第 1 轮 · 共 1 步 / Turn 1 · 1 step" (new tip.turn1 key) instead of the
last step's index, and a record missing stepCount/turn degrades defensively.
The tip no longer shows the request time or the reply preview: the replyTips
prop, brief.replyTipsOf, the contextView wiring, and the tip.actual i18n keys
are removed.
Closes#34
Both total and delta modes now read off a full 0 / ¼ / ½ / ¾ / 1 graduation:
the axis gains the missing ¾/¼ labels (delta marks carry the value at each
fixed height on the uniform px-per-token scale), dashed guides align the bars
with every mark (darkened to --dsw-alias-border-l2), and a solid zero baseline
anchors the '0' label in both modes. A delta quarter mark whose label box would
overlap the zero label yields entirely — label and guide together.
'en' only: the donut center sub reads 'Active Time', and the per-row call
count moves the multiplication mark before the figure with a space
('Model calls × 5' over 'Model calls 5×'). The zh forms (总活跃时长,
'71次') are unchanged.
A DOM overflow probe over the tab's body cards at 280-620px column widths
flagged two real leaks beyond the head cards already fixed:
- The trend card's turn strip drew its full content width (bars x 16px) as
a sibling of the chart scroller with no clipping, so zebra blocks painted
past the card edge into the neighboring column on long sessions. The
strip now clips at the chart wrap; the visible-slice label sync is
untouched.
- A browser Section head with a long mono call name (MCP tool names have no
break points) pushed the status/meta group out of the section card. The
name now ellipsizes and the Section label carries a title tooltip; the
file card's op-log tool chip gets the same max-width + ellipsis + title.
Probe is clean at 493/373/280/620px columns after the fixes.
1080p with the host sidebar open, or OS display scaling at 125%, squeezes
each head card below ~320px of content width — the donut legends' figure
columns collapsed to ellipses and the share column spilled past the card
edge. The head row keeps its equal-fourths single band, but each card now
is an inline-size container: below the 320px threshold the donut cards
stack the ring over the full-width legend, the context card's 2x4 grid
folds to 2 columns, and the legend clips inside the card instead of
overflowing it. Cards at or above the threshold render exactly as before,
and engines without container queries keep the wide layout.
The plugin-info rows and the slice rows' figure column also carry title
tooltips so an ellipsized value stays recoverable at any width.
Verified at 1040/1244/700/1660px pane widths with a DOM overflow probe:
no element crosses its card boundary in any case.
The headers fold helper now asserts every folded state is lossless JSON via
the framework's own snapshotJsonValue, matching the timeline specs; a new
hostile case covers the wrong-shaped plain JSON a committed log can hold and
pins verbatim survival of empty-object tool schemas. A comment on the
verbatim schema entry documents its session.append purification dependency.
The four styled DOM bubbles (stat cost tip, brief tag tip, file-card
delta tip, chart hover tip) each carried the same hand-rolled chrome
(layer-2 fill, hairline frame, 6px radius, float shadow, hover reveal).
A shared .lc-tip rule in base.css now owns that chrome — tips pair it
with their own class and keep only local positioning, width caps and
whitespace. The chart tip keeps z-index 5 and nowrap locally.
Token audit against dsh's ui-theme catalog:
- modal backdrop: hardcoded rgba(0,0,0,0.45) now rides
--dsw-alias-bg-mask-1 (the harness Modal and our own lightbox mask),
fallback keeps the old value on older dsh builds.
- monospace stacks (error message, call name/state, param type, arg
value) now ride --ds-font-family-code so code text matches the
harness code font; the fallback stack stays for older builds.
- tip shadows ride --dsw-shadow-lv3, the floating-layer shadow the
harness primitives use, fallback keeps the old tight shadow.
Kept as-is: the neutral translucent grays (128,128,128/x) for tracks
and zero lines and the violet category accents (#a855f7/#8b5cf6) —
the catalog has no tokens with those semantics.
The single 576-line global sheet grew with every card (trend chart,
browser, file activity, agent graph...), so every component change
opened the same shared file. Cut it mechanically into 14 cohesive
sheets under src/client/styles/ — one per component surface plus a
base sheet (root/card chrome, the lc-gran toggle, list anatomy,
error states, the composer-hiding rule).
Rules are byte-identical to the previous single sheet; index.ts
imports them in the original section order so the cascade is
unchanged (base first — later sheets re-tint the shared gran chrome
at equal specificity). bundle-smoke.mjs now asserts one style tag
per sheet instead of the single dsh-context/styles.css tag.
Zero buckets stay hidden once any usage is reported — the always-zero
cache write no longer shows on DeepSeek sessions — while the empty
state keeps the four-row fallback. Billing and cache-hit accounting
are unchanged; writes still render when providers report them.
The settings shell keeps its open/section state in private component
state and offers no plugin-facing open face, but the card registered on
settings.plugin.item is ours. After the jump's settings-trigger click
survives, it raises a short-lived in-bundle expand request; the card
consumes it on mount and lands open, scrolled into view. Degraded jumps
raise no request, and the request expires in 5s, so no stale signal
surfaces on a later manual visit.
- Lead with a three-entry table (Context tab, /context command, settings)
and a card-to-question table for the dashboard.
- One section per card with a fresh screenshot and short bullets; keep
the interesting bits (step brief, compaction marks, Delta mode, tool
source chips, image payloads, vs-previous-turn diff, agent network).
- Trim wording by ~40% and add a short 'Good to know' block.
The harness settings panel keeps its open state inside the shell
component and exposes no plugin-facing open face, so the entry drives
the real chrome best-effort: click the sidebar's dialog trigger, then
the Plugins section nav row (matched by its shipped en/zh labels).
Every step is individually guarded - a shell without a matching
trigger or section degrades the whole jump to a silent no-op, and no
path can throw into the render.
The host fold derives whole-session timing totals from the durable
step/tool lifecycle (step/start -> assistant/message -> step/end;
tool/call -> tool/result by callId): step wall time, the model-call
slice, and per-call tool durations with a bounded per-name tally
(stateVersion 11; cached rows refold from the log).
The head row becomes four equal cards: an eight-cell context grid
(shape stats, event tally, cost estimate with its rate tooltip), a
token ring around the cache-hit share, and a timing ring splitting
model calls vs tool runs. Legend rows pair each donut slice with
its label, quantity, and share - no bars; the ring center type
scales with the ring so it never touches the stroke. Wire guards,
i18n, styles, and hostile-input test fixtures included.
Every one-item category (not just system) now auto-expands its only row
when the category opens, so one click lands on the content directly:
the lone tool schema and the lone surface node join the system prompt.
Multi-item categories keep opening collapsed.
Add an icon to each finalized reply's assistant-actions row that opens
the Context tab pinned to that reply's turn: the click resolves the
reply's request seq off the served node seat, records it in a per-session
relay, and activates the tab; the view consumes the relay once its
projection data is in, flips the trend chart to turn bars, pins and
centers the turn's aggregate, and resets the shared scroller. Every hop
degrades quietly (unknown seat, unresolvable seq, missing tab, hostile
nodes, aged-out turn).
Hovering a category in the Context browser (or the overview legend/bar)
now lights that category across the trend card: the matching segment in
EVERY chart bar (total and delta modes), the detail panel's composition
bar, and its per-category rows.
The chart highlight rides a plain container attribute (`data-catdim`) +
static per-segment `data-cat` keys, so the dim/brighten pairing is pure
CSS and the memoized bars never re-render on a cross-card hover. The
detail bar mirrors the key one-way with its tooltip off (a cross-card
hover must not float a second tip), and the detail rows reuse the
legend-chip idiom: tinted background, bold label, ring on the color dot.
The overview's 'free' key stays filtered, as in the browser's live link.