One tiny recorded text turn drives three whole-page concerns:
- workspace flow over the real wire: the empty-state hero's first send
materializes a real Workspace + Session (the jsdom workspace-flow suite
pins this state machine over the fixture client; this scenario pins it
through HTTP RPC + SSE + the gateway). Durable proof: the session
header's cwd is the create-by-name target <workspaceRoot>/workspace.
Adds the hero waiting-state aria golden.
- reload recovery: collapse the sidebar (persisted dsh.layout.panels),
page.reload, and the surface comes back whole from persistence alone —
layout collapsed, selection restored (dsh.sessions.current), the
recorded turn re-rendered from session.history with zero model calls
(the drained replay cursor makes any stray request fail loud at close).
- dark mode: no product control flips the theme yet, so the scenario
drives the ThemeService's entire DOM contract — body[data-ds-dark-theme]
— and pins the shipped cascade: the alias token flips, a painted surface
repaints, and removing the attribute restores the light sample exactly.
TODO(web-theme-gesture) upgrades to a real settings control; no theme
golden per the lane's scope ruling (aria is color-blind).
Agent Note scenario list extended in both languages; pairing re-recorded.
One two-turn seed (turn 1: bash + two parallel reads in a single assistant
message; turn 2: a markdown-heavy reply) rendered cold through the
seeded-history pattern — zero model calls — serving four surfaces:
- sidebar search: client-side title filter; asserted only after the durable
title lands with the attach baseline (a cold SessionSummary carries no
title — search matches the displayTitle the user sees). Negative query
empties the tree, positive narrows to the match + its force-expanded
group, clear restores.
- Trajectory tab: turn sections, the step group's tool mix ('bash read×2'),
and a view-area aria golden.
- Waterfall tab: span stats header + one lane per span. The P-I fold counts
a turn-0 prologue span (only assistant/steering nodes carry a turn
number) — pinned as-is; real spans are P-III per the view's ledger.
- details column: the bash toolview row routes click to openDetails;
open/closed is asserted on the frame's data-details-collapsed attribute
because close collapses the grid column to width 0 without unmounting
the subtree (hidden, not absent, is the contract).
Agent Note scenario list extended in both languages; pairing re-recorded.
Both languages: the three new scenarios (live-interactions overrides,
question-composer takeover, wire-level steering), the product-delta list
({ patches } override form, the carried-failure fix, the llm-retry row),
two new Deferred items (web error surface, composer steering gesture),
and de-hardcoded scenario counts; pairing re-recorded.
Five browser e2e scenarios over the existing keyless lane, one recorded
base fixture per spec family:
- live-interactions: one tool-free recorded turn + per-run override
sidecars authored in the spec (content single-sourced from the fixture
via deriveReplayScript, minted into a spec-owned temp dir). Cancel uses
a hang patch with a readyFile marker — the marker proves the stream is
parked mid-turn before the Stop click, so mid-stream cancellation is
deterministic by construction (turn/end 'aborted', composer re-enabled).
AUTH pins the non-retryable path: turn/end 'error', zero llm/retry
events, composer recovers; FIXME(web-error-surface) marks the found
product gap (no error copy renders — the client consumes no agent/error
frames and a pre-chunk failure freezes no partial). SERVER retry appends
the fixture's own success after an injected throw and proves llm-retry
end-to-end in the browser via the durable llm/retry record.
- question-composer: the shipped ask_user_question takeover blocks the
turn mid-step on the real userInteraction seam; the test answers through
the composer (the one sanctioned model-content-reactive drive step: the
turn cannot complete without it) and the tool result carries the answer.
Adds the composer waiting-state aria golden.
- steering: steers mid-turn while the composer blocks the step (the
deterministic mid-turn window). The steer rides the real wire
(session.prompt mode:'steer' POSTed from the page; the locked composer
has no steering gesture yet — TODO(web-steer-composer)); downstream is
all product: gateway -> Agent.steer -> step-boundary drain -> durable
steering/message -> SSE -> badged interjection bubble. Record mode
rejects a fixture whose live reply ignored the steer.
Scaffold gains the replayOverride passthrough; specs register in both
tsconfig planes (client exclude, host include).
The ReplayConfig.overrideFile JSDoc still described only whole-script
replacement; it now names both sidecar forms and links ReplayOverrideDoc
(config catalog regenerated: source line shifted).
The web tree had no transient-failure recovery around the loop's model
calls; the TUI agent-spine composition already mounts llm-retry. Same
defaults (2 retries, 500ms->10s backoff). The browser e2e retry scenario
drives it end-to-end: an injected SERVER throw at call 0 recovers through
the durable llm/retry record and completes in the transcript.
markLlmAdapterFailure gated the own-`failure` data property on instanceof
HarnessError, which drops the validated facts exactly when class identity is
lost — two copies of this package in one process (e.g. a source-plane replay
harness throwing into a lib-plane boot) make the replay-thrown LlmError's
SERVER/AUTH code arrive as UNKNOWN and defeat llm-retry's retryable-code
match. The snapshot is already validated field-by-field and cross-checked
against the error's own code, so honor it on any Error.
The override sidecar now accepts { patches: [{ at, entry }] } alongside the
legacy whole-script ReplayEntry[] replacement: the JSONL-derived script is
kept and only the named call indexes are swapped (at == length appends, for
a retry attempt following an injected transient throw). Out-of-range or
non-integer indexes fail loud with the derived length in the diagnostic.
This is the mock-LLM error capability the web e2e scenarios drive: 'call N
throws AUTH/SERVER, everything else replays as recorded'.
Integrate the Commander adapter with master's `dsh web --workspace-root`
(workspace-aware session flow).
- args.ts: add `--workspace-root <path>` to the web subcommand; WebInvocation
carries workspaceRoot.
- web.ts: keep the adapter-parsed signature, take (host, port, dev,
workspaceRoot) and pass workspaceRoot through to AppCLIEntry (drop master's
re-added parseArgs and CLI host/port validation — the schema owns those).
- bin.ts forwards invocation.workspaceRoot; args.spec + the Agent Note pair note
the flag.
Integrate the Commander adapter with master's headless rework (dsh -p now boots
through AppCLIEntry — the same cordis.yml composition as dsh web, web-observable
while it runs — via toFetchHandler(ctx.apiProxy)).
- headless.ts: keep master's AppCLIEntry-based body but take the adapter's
pre-parsed `task` (drop the re-added parseArgs). Old startHost path and the
now-deleted packages/host/runtime dependency are gone.
- remove the stale packages/host/runtime dir left on disk by master's deletion
(mirrors the earlier packages/ui/acp cleanup).