Expanded files read as contained layer-2 cards with a hairline frame, the
per-operation log hangs on a tree-guide rail under the chevron, count badges
and op tool chips become pills, and rows get roomier padding with tabular
timestamps.
The bottom-right card no longer lists model-visible messages; it answers
what the agent DID to the user's files up to the picked step. Tool-result
nodes (live + archive) join the conversation snapshot for call arguments
and fold into one row per file with per-purpose counts (read/written/
searched), estimated line deltas from edit/write arguments, multimodal
and directory form glyphs, and failure dots. Header chips double as
purpose/form filters next to a path search box; a row expands into the
file's operation log, and each operation reveals the exact tool result
in the Context browser. The card scrubs live with the trend chart pick
(exclusive next-request bound); ops whose paths aged out of retention
are honestly counted in a footnote.
The chip row lived in CurrentComposition, was fed the fold's per-tool
toolList summary, and the chips focused the Context browser on a tool
via a one-shot toolFocus callback. The browser already exposes the
tool schemas through its headers section, so the chip row was a
duplicate entry point that no longer pulled its weight.
Removes the lc-tools DOM block, the toolFocus/onToolFocusHandled
prop wiring across ContextView/ContextModal/Browser, the
'tools.top'/'tools.more' translations, the .lc-tools* CSS, and the
now-unused toolList summary across the wire + state schemas
(stateVersion 9 -> 10). estimateToolSchema stays because headers.ts
still prices individual tools for the browser's tool-schema cards.
The Context browser joined full content against the chat conversation
window; when a surface node's seq sat outside it, the fallback paged
blindly backwards through session.loadOlder() (up to 20 pages of 50
messages prepended into the shared chat window) and finally dead-ended
on a static note.
sessions.history already supports seq-anchored pages cut on whole
append-origin message boundaries, so ONE call with beforeSeq: seq + 1
returns exactly the page holding the event whenever the durable log
still has it. The browser now:
- fetches on miss with that single anchored read and maps the raw page
into the node shapes it already renders (new client/historyPage.ts:
user messages, assistant blocks, tool results paired with their
in-page call head, compaction checkpoints with their summary);
- merges fetched nodes under the zero-cost window join (join wins);
- reports honest states instead of one dead end: loading, absent from
the session log (compacted or cleared), failed with a retry button;
- drops the sessions.provide contribution and the MAX_AUTO_PAGES
machinery entirely - the chat scrollback is never mutated again.
Hosts without the connection api keep the static preview-plus-hint
degradation.
The source-level suite needs no lib/ artifacts, so PR checks test
first and build afterwards solely to feed scripts/bundle-smoke.mjs;
the release workflow gains the same smoke step between test and
publish.
All 19 root-level specs and the fake-React viewBed helper are
superseded by the source-level suite (606 tests, 100% per-file
coverage). The packaging behaviors they pinned (bundle handoff, CSS
channel, HMR disposal) live on in scripts/bundle-smoke.mjs.
The old suite exercised the built lib/ bundle, so v8 remapped almost
nothing (8% src coverage) and every run needed a build first. The new
suite imports src/ directly under two vitest projects (node for
host/shared, jsdom for client), rendering real React 18 trees against
the real dictionaries and ui-primitives, with faithful in-memory
harness faces (cordis inject/effect, locale chain, slots, sessions
provide) instead of plugin mocks.
- 43 fine-grained, stateless, single-module spec files (606 tests)
forked across workers: ~2.5s wall, no build step
- v8 thresholds at 100/100/100/100 per file over src/**
- host specs drive the real projection definitions (init/apply/view)
plus one full cordis + SessionStore + SessionProjectionRegistry
integration; the framework's snapshotJsonValue pins the
projection-cache precondition on every intermediate fold state
- oxlint's src ruleset now covers tests/** as well
- typecheck covers the tests via tsconfig.tests.json
The session-log divergence check runs under bun against src/ directly,
tracking the working tree instead of the last build. Also add
scripts/bundle-smoke.mjs (pnpm run test:bundle): the one check that
must exercise the built bundle — module-loader handoff, CSS channel
tagging, registration disposal and HMR style-tag semantics in jsdom
with real React.
Every annotation carries its evidence inline (guard dominance chains,
the 8:1 aspect clamp making the wide-solve arm unreachable, a
500k-dimension fuzz with zero hits). browser.tsx also drops two
redundant `?? null` in JSX render positions — undefined and null
render identically.
SlotRegistration.inject is invoked without a session id for root-scope
keyed slots (settings.plugin.item), and slot components receive
harness-injected props beyond sessionId (useProjection, hooks). The
narrower types made the plugin's own registrations fail a strict
typecheck of the test harness's faithful slot registry.
Introduce `pnpm run coverage` to run vitest with v8 instrumentation,
generate a browsable HTML report under ./coverage/, and gate it from
git. Scope coverage to the src tree only: lib/ is the tsdown build
artifact and has no executable surface to cover. Exclusions are
limited to pure type modules (ambient .d.ts, src/shared/types.ts,
src/host/compat.ts) so the summary reflects runnable code.
Brief lines now decompose every node into a fact tag plus preview text,
mirroring the browser element row a click reveals: tool inputs lead with
the tool NAME chip, replies chip their call breadcrumb, injections tag
the form, user messages tag image attachments. Inputs and the reply share
one nodeChip renderer — compact clickable chips for inputs, a single
row-grown chip for the reply.
New hero screenshot (context-trend.png) showing the User/In/Response
rows; the section is now bulleted with the step brief first. Drop the
stale history-step-hover.png (unreferenced, predates the tooltip's
reply preview).
'History' in dsh names the chat message log itself, so the trend card's
old names (Context History / 历史趋势) read as a message list; the new
names align the two locales and match what the card shows. The overview
card follows the same pairing (当前构成 -> 当前上下文, already
'Current Context' in English).
Each history bar now carries its semantic identity below the detail head,
derived client-side from the served surface nodes (a request's seq IS its
response's node seq):
- User row: the message that opened the turn, recalled on any of its steps
- In row: what newly entered the context (tool-result chips, err dots)
- Response row: the reply text, the call breadcrumb for text-less replies,
and the call suffix recovered via the conversation join for text+calls
replies (the fold keeps calls only on text-less nodes)
Plus: one-line reply previews in the chart tooltip, and a click-to-reveal
bridge that opens the row's node in the Context browser (one-shot nodeFocus
prop, same hand-back pattern as toolFocus; reply nodes resolve on the NEXT
step's surface, per the assemble seq window).
No host/wire changes; steps beyond node/archive retention degrade silently.
Shared call-argument summaries extracted to callSummary.ts (browser reuses).
The number column flipped between 44px (zero) and 52px (+n/-n) every
time the user hovered a different bar in delta mode, shifting every
neighbouring row by 8px. Lift tabular-nums and the wider width onto the
base .lc-detail-num; the zero cell right-aligns in the same slot.
Fixes#19 (number column half).
scrollbar-gutter: stable keeps the horizontal gutter unconditionally, so
the trend chart card no longer jumps when the overflow toggle fires
(solid-scrollbar platforms: Win/Linux Chromium). The 8px padding-bottom
stays as a safety net for browsers that under-reserve.
Fixes#19 (chart card half).
- contextView: drop the tick-based locale re-render subscription; the
harness slot outlet subscribes the LocaleFace revision and re-renders
every entry on a locale switch (verified back to dsh 0.1.0-rc.7), and
the bound t reads the active locale at call time
- browser: delete the never-read catColor map; countOf now takes the
prebuilt byCat/prevByCat maps instead of rebuilding byCatOf per call
(up to 13 map builds per render)
- categories: export CAT_COLORS built once from CATS; nodes reuses it
instead of rebuilding per render
- trendChart: deltaOf rest-destructures prompt/output out instead of
spread + two deletes
- statsBoard: hoist the pure cell helper to module scope
- services: drop the now-unconsumed LocaleService.subscribe face member
- tests: shift ContextView hook-slot indices after the tick removal,
drop the dead stats.recycleSub fixture key and the dead locale
subscribe stub
job_output always terminates its read with the tool-jobs status line
[status: killed] / [status: failed, detail], and a background job that
dies settles with isError false — so the read showed a green OK for a
dead job. tailStatusOf now treats the two terminal failure statuses as
failures; completed/running/stopping stay notices.
This goes beyond the official chat UI, which renders job_output as a
generic card and shows OK too.
tool-bash-persistent/tool-pwsh-persistent append shell-death markers at
the end of a result's text with isError still false: [shell killed by
signal: X] and [shell exited: code N] ride LAST, after the [exit code: N]
of the command whose failure killed the shell — so the command marker
alone no longer anchors at the end and was missed.
tailStatusOf now parses the shell marker first and re-checks the
preceding text for the command's exit code, so the pill still surfaces
it. A clean shell exit (code 0 or code-less) loses the session but is
treated as a notice, not a command failure.
dsh settles a failing command as a completed call (isError stays false;
the exit status is result data), so err/isError alone never flags a
non-zero exit and the context browser showed a green OK where the chat
row shows red. Mirror the chat's terminalFailed: a result is failed when
its text ends with dsh's [exit code: N] or [killed by signal: X] marker.
The exit-code match is now end-anchored like dsh's parseExitStatus, so
marker text quoted inside the output (e.g. a cat'ed log) is not a
failure, and the FAILED pill always surfaces the parsed exit code
instead of gating it behind err.
Delta mode previously took per-category magnitudes for the bars while
the detail panel below still showed cumulative totals. Now:
- The chart draws diverging stacks: growth piles up from a solid zero
baseline, shrinkage hangs down from it, with the zero line splitting
the bar area proportionally so the px-per-token scale is identical on
both sides. Axis reads +max / 0 / -max.
- The detail panel pairs the active request with the previous displayed
record (same pairing as the chart): a signed net chip replaces the
provider usage pills, category rows show signed figures on the
success/error tokens, and each row's mini bar diverges from a center
zero hairline, echoing the chart.
Aligns with the token-meter vocabulary of recent dsh releases (signed
token delta) and with the existing zh label 增量. UI labels, i18n keys,
settings schema, client logic and tests all switch from diff to delta.
The settings schema is now loose on defaultTrendMode so a stale
persisted value degrades to the default instead of throwing, and the
client ignores unrecognized values the same way.
Restyle the request detail header for a cleaner, more consistent look:
- even 8px gaps and center-aligned items on the single wrapping row
- time pinned right with tabular figures
- provider figures (actual prompt / output / cache) as uniform neutral
pills with tabular numbers, replacing the plain spans and the two
same-green accent colors
- marker chip tinted with the warn color, matching the event list chips
Drop the heuristic estimate from the step header; show the provider-reported
cache-hit share of the billed prompt (cacheRead / prompt, truncated to two
decimals) instead. The host folds each request's cacheReadTokens into its
record (stateVersion 9); the stats board reuses the same formula via a shared
format helper.
Compress the CSS-channel note in tsdown.config.ts, the vitest lane
explainer and the release workflow header; fix the install-hooks header
(git only consults .git/hooks when core.hooksPath is unset); slim the
social-preview generator and bundle-patch comments.
Remove assertion restatements, act narrations and decorative section
markers; keep fixture math, harness-gotcha rationale and slot maps.
Fix stale comments: ctxSlots maps (trendMode shift), a dangling fixture
fragment, and the cardRig 'expanded via header' claim.
Apply the full-repo comment audit to the host half and shared wire types:
delete restatements and decorative headers, compress rationale comments to
one line, and fix stale references (agent-loop buildRequest, live-surface
image cell, five->four priced categories). Kept only contract notes,
invariants (plain-JSON precondition, shadow-seqs protocol) and verified
dsh-mirror pins.
The turn strip renders each label inside a nested `lc-turn-label` span
(added with the scroll-centering work), so the label assertions must read
through textOf instead of expecting a bare string. The scroll/layout
handlers also call updateTurnLabels (querySelectorAll over the scroller),
which the fake scroll elements must stub — an empty querySelectorAll keeps
the label-centering a no-op without touching the asserted behaviors.