Apply the AGENTS.md comments guideline to every .ts under tests/: remove
what the code already expresses, keep only what it cannot.
Removed: spec-header coverage inventories the describe/test tree already
names; comments restating the next line's assertion or its message; helper
JSDoc whose body already says it; within-file duplicates; five stale
claims (trendChart's "six aggregated turns" against agg.length === 8,
agentGraph's "six categories" against CATS' seven, fold-timing's "cheapest
call LAST" against a loop that feeds it first, fold-surface's unreachable
plain-JSON note, contextView's "two turns" for a one-turn fixture).
Kept and condensed: the projection-freeze class, strict wire/state-schema
hazards, retention and pricing windows, pairing rules, hostile-input
rationale, fixture and expectation-array ground truth, jsdom mechanics,
and issue-numbered regressions.
Comment lines 2604 -> 2014 (-22%); 85 module headers kept, 30 coverage
inventories dropped; every surviving comment line is within the 140-column
limit, with 36 wrapped lines joined onto fuller ones.
No logic, string, identifier, import or directive changed: a comment-strip
gate proves each file's comment-free code is byte-identical to HEAD, and a
line-level proof pairs all 845 changed lines with an unchanged code part.
pnpm run lint && pnpm run test (106 files / 2061 tests, per-file 100%
coverage) && pnpm run build all pass.
Merge consecutive short comment lines in tests/** up to the 140-col limit
without touching their text: comment-stripped content, comment text (whitespace
normalised) and line-comment runs are all proven identical per file. This is the
line-count half of the cleanup only; the content pass over tests/** is still
outstanding.
The mask was inset on the left sidebar alone, so with the right
Sidebar open the dialog centered over the main column plus the right
panel and its right half disappeared underneath. measureDock now
parses the frame template's trailing px track too, clamped to the
space the centre track's declared minimum leaves: on the minmax
generations the trailing bound is only the right panel's bid, not the
solved width — the frame lets the grid squeeze the right track before
the centre drops below its minimum (measured live: template
minmax(0px, 495px), solved track 420px at a 1100px viewport), so an
unclamped parse over-insets the mask in exactly that squeeze regime.
The V3 line's fixed trailing track and every degrade-to-full-mask
branch are unchanged.
The card width now resolves against the backdrop (100% instead of
100vw), so when both sidebars bound the mask the card clamps inside
the main column instead of poking underneath either edge. Verified
live at 1280/1100/900/390 px with the right panel open, closed, and
across its track removal at phone width.
Add useTimelineSource cases for the cold read rendering from its own slim
head, the retry after a failed/absent/headless cold read, and a pushed head
taking the shared per-session store back. Surface the retryable failure note
in the Context tab and /context modal specs, isolate the page-lifetime
detail stores between tests, and record the no-stall contract in
docs/compatibility.md.
The supported range is now two baselines - dsh-v0.1.5-rc.1 (session
format V3) and dsh-v0.1.7-rc.2 (V4) - enforced by the runtime gate at
BASELINE_DSH_VERSION = 0.1.5-rc.1; below it the fallback units and the
upgrade modal take over (0.1.2/0.1.3 hosts were previously served by the
real folds).
The pre-V3 log shapes are dead code on every supported harness: the
running harness migrates historical logs on every read path (or refuses
the session wholesale), and 0.1.5-rc.1 rejects a request/header carrying
header.system outright. The fold therefore drops the V0/V2-only paths -
the header.system envelope system prompt, assistant/chunk floods,
{start, end} replacement endpoints, tool/code-dispatch - and the state
fields only they wrote (systemsFromHeader, the stepStart decode
accumulators). stateVersion stays 20: no supported log folds
differently, and pre-V3-era cached rows are discarded by the projection
cache's identity gate (and the strict stepStart sub-schema) instead of
served. The headers unit folds metadata-only epochs (envelope
systemTokens write dropped; the v1 cached-row read-compat and its pinned
stateVersion 1 stay); replaceRangeOf speaks startSeq/endSeq only; the
browser's epoch fetch maps request/header to tools and system/message to
the prompt text.
tests/baselines.ts re-fits to the two entries (the right Sidebar seam is
required on both), the compat matrix drives both tags' REAL sources
(canonical logs re-shaped to V3, the hostile probe renumbered past them),
and the version fixtures pin the new floor. Manifest: dshReleases
{0.1.5-rc.1, 0.1.7-rc.2}, dsh peers >=0.1.5-rc.1 (satisfies the 0.1.7
admission check under includePrerelease), devDeps pinned to the
0.1.5-rc.1 surface plus the ui-primitives runtime closure the client
lane inlines (test-only), and the release workflow fetches the two tags.
docs/compatibility.md is rewritten around the two-line matrix; README,
AGENTS.md and the issue template follow.
Also riding this move, from the audit pass: the fold re-proves an inject
source's form before it books the event record (the harness validates
`kind` but not `form` - a hostile one previously failed the strict
wire/state schemas on every delivery, the #44 freeze class), the headers
unit guards a data-less envelope (it has no try/catch of its own), the
schemastery schema annotations drop the explicit z<T> wrappers the 3.18.4
Mode generics reject, and the version-history note records why
stateVersion is deliberately not bumped.
The three local until-polling copies let the debounced detail updates
land outside act — React warned 19 times per run. They collapse into
one shared act-per-tick until in the kit: timers and promise chains
only fire while a tick's sleep is awaiting, so the updates stay
act-attributed and commit as each scope exits.
The four browser specs that arm the fetch-on-miss retry with a
deliberately failing fetcher silence the production console.warn the
failure legitimately emits, restored by a file-level afterEach.
A default-reporter run now prints zero stderr.
The split generation's detail transport rode connection.rpc.handle, whose
registration reads webServer off the connection service's own context —
cordis's strict-read guard rejects that for plugin callers, so the channel
never armed and every detail read fell through to the static fallback (405).
Mount the handler through connection.fetch.register instead (the same
exact-route seam the harness's own file-upload and media-reference routes
use), riding the authenticated /api fence; the client POSTs it same-origin.
The detail payload now also carries the slim wire head at the same fold cut
(the composition scalars the Agent network's cold-node ring fetch renders
from); a missing or malformed head drops out alone, never rejecting the
payload the detail cards consume.
The fold buckets all three skill-content shapes the harness injects — the
<available_skills> catalog digest, a user-explicit /name invocation's
instructions message, and the content a skill-tool load returns — into a
new 'skill' composition category instead of burying them in inject/tool
(closes#66). Skill-tool loads keep their tool identity stamped, so the
call tallies, hit counts, and failure marking are unchanged.
stateVersion 18 → 19: the per-category sums change shape, so cached rows
refold from the durable log on upgrade (docs/compatibility.md).
The trend chart, overview, Token donut, stats, and the Context browser all
gain the seventh bucket (orange-500); the browser's skill section pins its
rows like injects, so long sessions keep listing what the sums count.
New 'Tool Schemas - Default sort' row in Settings -> Plugins -> dsh-context
(ahead of the File Activity one): size, call count, or name, persisted per
user through the dsh-context settings namespace. The Context browser reads
it as the mount-time default for its tools toolbar; in-toolbar toggling
stays session-local and never writes back. The default default is now call
count. Degradation mirrors the existing preferences: the host schema is
loose (a stale persisted value falls back to the default), the client drops
values outside the vocabulary, and a host without the settings surface (or
a section missing the field) keeps the schema default, so the tools list
stays fully functional either way.
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.
Every client file reached hooks and element types through the central
require seam (src/client/react.ts) and spelled them React.useState /
ReactNS.ReactElement. Import the members directly instead — named value
imports for hooks, inline type-only imports for element types — and let
the bundler own the single require('react'), which is exactly what the
platform module table answers.
This needs JSX off the classic runtime (it required a React identifier
in every tsx scope): tsconfig moves to jsx: react-jsx, so the bundle
sources jsx from 'react/jsx-runtime', already seeded by every supported
harness (verified against the dsh-v0.1.2-rc.1 platform.ts). The bundle
smoke's fake module table gains the specifier to stay a faithful shell
stand-in. h() survives as a local createElement alias at the slot
render-prop seams and the lightbox portal imports createPortal from
react-dom directly.
Verified: the built client.js requires react, react/jsx-runtime, and
react-dom exactly once each; the compat lane (bundle smoke + the
real-baseline matrix) passes against the fresh artifacts; the full gate
stays green with per-file coverage at 100%.
The contextTimeline projection value rode every harness delivery channel
WHOLE: each session.list row, every control baseline, every follow
snapshot, and a full-value broadcast on every state change (with a
full-value schema parse per push). The heavy collections (request records,
context events, the served surface window, the removed-node archive) are
only ever rendered by an OPEN Context tab/modal, so they moved off the
wire value.
Wire head (buildTimelineHead, ~1KB): the composition breakdown, the stats
board's precomputed counters (turns/steps/injects/compactions/prunes), the
newest request's billing summary (the headline's derived anchor), the
timing/cost totals, and `detailRev` — a revision marker that bumps exactly
on detail-mutating folds, the open tab's refetch signal.
Detail channel (host/detail.ts): the pre-v0.9 generic Connection RPC shape
returns as one narrow endpoint, `/dsh-context` `detail`, serving the
collections off the registry's live fold state (`stateOf` — no second
fold, no private copy). A nested `ctx.inject(['connection', 'sessions'])`
arms it load-order-free; a deployment whose faces never compose keeps the
inline wire value unchanged. The baseline-gate fallback path is untouched.
Client (client/timelineSource.ts): one per-session detail store shared by
the tab and the /context modal — fetch on open, then debounced
trailing-edge refetches whenever the pushed detailRev outruns the served
detail (single-flight, latest-wins by revision, refold reset, failure
backoff, absence stops trailing). Detail-driven cards show a pending/retry
note instead of a misleading empty state; the counters and headline render
off the head immediately. Both wire generations are reconciled per value
(new client + old host renders the inline value untouched; old client + new
host degrades to empty charts in the stale-watch window, never a white
screen). The chat-jump relay holds its one-shot pin until the detail lands.
Fold state keeps the full shape: no stateVersion bump, no cached-row
invalidation (the slim view is recomputed per read for cold checkpoints).
Compat matrix gains the detail-channel seam probes (HostConnectionRpc,
the browser caller, the registry stateOf), verified against the
0.1.2-rc.1 tag.
Measured over 263 real session logs from a live deployment, plus live RPC:
- per-session wire value: 84–150KB (top sessions) -> 0.6–0.9KB (~100-165x)
- all sessions aggregate (session.list rows / control baselines):
4.4MB -> 0.2MB (~22x)
- per-client push traffic over one big session's lifetime:
157MB -> 1.8MB (~87x), and the per-push full-value zod parse shrinks
by the same factor
- detail payloads now flow only for the session being viewed, only while
its tab/modal is open (120KB measured live for the biggest session);
background sessions cost zero detail traffic
Verified visually against the running harness (big/small/cold sessions,
a live turn refreshing in place, past-step reconstruction, header epoch
and tool schema reads, the /context modal, the Agent network card) with
byte-identical card output. 1110 tests, per-file 100% coverage.
The fixed backdrop masked the whole viewport; inset it by the sidebar
track parsed off the app frame's inline grid template (the sole inline
gridTemplateColumns in the dsh client, identical on every supported
baseline), so the dialog centers over the main column and the sidebar
stays visible and interactive. An unresolved frame degrades to the
full-viewport mask.
The measure runs in a layout effect (no 0-inset first paint) and, while
open, a MutationObserver on the frame's style attribute follows sidebar
drags, collapse toggles and narrow-viewport re-solves. A per-baseline
compat probe pins the frame seam.
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 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