Commit Graph

51 Commits

Author SHA1 Message Date
Tianyi Cui
f1b7d52a77 fix review findings: hostile code accessor + swallowed teardown
Both ds-review-bot findings were real:

- markLlmAdapterFailure's carried-facts cross-check read error.code
  directly; a foreign Error with a valid own failure payload but a
  throwing code accessor would replace the original adapter error with
  the accessor exception, breaking the error-identity guarantee. The
  read now goes through foreignErrorCode(), which contains the trap and
  falls back to the normalized snapshot (test: hostile code accessor
  beside a valid failure payload -> original identity kept, UNKNOWN
  facts).
- live-interactions' afterEach caught scaffold.close() into undefined,
  silently disabling ReplayHandle.assertConsumed() — the fixture-drift
  tripwire — and hiding cleanup defects. Teardown now runs every step,
  collects failures, and rethrows (AggregateError when several).
2026-07-26 14:12:52 +08:00
Tianyi Cui
4b06e9df99 Merge remote-tracking branch 'origin/master' into web-e2e-interactions 2026-07-26 13:08:39 +08:00
Tianyi Cui
ea1d8d06b3 test(web): pin every scenario end-state with an aria golden
Every spec now commits at least one golden and the interactive ones one
per distinct end-state (nine new .expected.md):

- live-interactions: cancel.expected.md (frozen partial + 已停止 marker),
  error-auth.expected.md (the prompt bubble alone — the committed artifact
  of the web-error-surface gap, the diff that flips when error rendering
  lands), retry.expected.md (indistinguishable from a clean completion —
  retries are deliberately invisible in the transcript).
- question-composer: answered.expected.md (the question resolved into its
  tool round trip plus the final reply, takeover gone) beside the existing
  waiting-state golden.
- steering: mid-steer.expected.md pins the accepted-but-INVISIBLE state
  (the loop drains steering only at the step boundary, so no interjection
  bubble exists while the question still blocks — if the client ever
  renders pending steers eagerly, this golden flips first) and
  settled.expected.md the badged bubble plus obeying reply.
- navigation-panes: waterfall.expected.md and details-open.expected.md
  (tool-name header, Input args, Output result) beside the trajectory one.
- lifecycle-chrome: reloaded.expected.md — rendering the same settled
  transcript from persistence alone IS the recovery claim.

Fixture inventories extended to the new closed sets; the Agent Note's
expected-outputs policy updated in both languages (per-end-state goldens
for interactive scenarios), pairing re-recorded.
2026-07-26 12:45:44 +08:00
Tianyi Cui
cbb5fc7a51 test(web): lifecycle & chrome scenarios — workspace flow, reload recovery, dark mode
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.
2026-07-26 06:08:08 +08:00
Tianyi Cui
4964e9c729 test(web): navigation & panes scenarios over one rich seeded session
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.
2026-07-26 05:15:47 +08:00
Tianyi Cui
07770f90f9 Merge remote-tracking branch 'origin/master' into web-e2e-interactions
# Conflicts:
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md
#	apps/web/tests/scaffold.ts
2026-07-26 04:10:37 +08:00
Tianyi Cui
04b7f517ae test(web): live-turn interaction scenarios — cancel, error, retry, question composer, steering
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).
2026-07-26 03:36:55 +08:00
imccyu
6cc68baddf Merge remote-tracking branch 'origin/master' into worktree-ws-more 2026-07-26 02:26:27 +08:00
imccyu
ad419065a0 test(web): stop pre-clicking the fixture group in the title snapshot
The Intent's current-group effect already expands the target workspace;
with intent no longer forcing expansion, the header click collapsed it.
2026-07-26 02:10:59 +08:00
Tianyi Cui
f1577d620e test(web): harden replay scaffold lifecycle 2026-07-26 00:33:46 +08:00
Tianyi Cui
ee25ccf38b Merge remote-tracking branch 'origin/master' into web-e2e-lane
# Conflicts:
#	vitest.web.config.ts
2026-07-25 18:19:22 +08:00
imccyu
c06cb2deec test(web): stabilize workspace snapshots 2026-07-25 17:46:05 +08:00
imccyu
9eb9c70a8a feat(web): add workspace-aware session flow 2026-07-25 17:45:26 +08:00
Tianyi Cui
fb17d5ec49 Merge remote-tracking branch 'origin/master' into web-e2e-lane
# Conflicts:
#	docs/config-catalog.md
#	docs/testing.i18n.yaml
#	docs/testing.md
#	docs/testing.zh.md
#	packages/host/runtime/src/start.ts
#	packages/support/acp-snapshot/README.md
2026-07-25 14:20:19 +08:00
Turtle
89254b7c56 Merge remote-tracking branch 'origin/master' into codex/trim-redundant-comments
# Conflicts:
#	packages/client/connection/src/index.ts
#	packages/host/webserver/tests/web-plugins.spec.ts
2026-07-25 13:06:40 +08:00
Turtle
fac6c35e9a Trim redundant source comments 2026-07-25 13:02:37 +08:00
imccyu
80cd8f54b5 refactor(web): collapse the shell boot into the AppWebEntry class
The four free functions in boot.tsx become one kernel class holding what
must exist before cordis: the parsed BootManifest, the ClientModuleSystem
instance, and the loading-page handles. Context/Loader setup runs in
parallel with the immediately-tier prefetch, but entry creation awaits the
prefetch: materialization is tree.import's synchronous require, so
cross-package require edges (i18n -> runtime/client) need every
immediately-tier factory registered first — unbarriered creation raced
10-25% of boots. The kernel adopts the modules entry (writes the
__DSH_MODULES__ slot pre-cordis, creates the entry first, skips its graph
row), and provide('modules') now lives in the adoption apply. apps/web
drops its host-package edges (composition is apps/cli's job).
2026-07-25 10:23:40 +08:00
Tianyi Cui
6d3c25f494 refactor(web-e2e): rename harness -> scaffold; add interaction coverage
The shared test module was named harness.ts inside a repo whose product
IS a harness — hopelessly ambiguous. Renamed to scaffold.ts with
launchWebScaffold/WebScaffold; tsconfig plane-split entries, the seam
JSDoc/README mentions, and both Agent Note languages updated.

Both scenarios gain a Playwright interaction step over the settled
transcript (after the golden capture, so committed aria surfaces stay
untouched): replay-round-trip clicks the reasoning fold open/closed over
wire-delivered state; seeded-history expands a read tool row rebuilt from
the cold log and asserts the recorded result text appears (read rows are
expand-in-place — rowExpands routes the click to the inline fold, not the
details column). test:web 30 passed | 1 skipped.
2026-07-24 22:18:19 +08:00
Tianyi Cui
d08050ab69 chore(web-e2e): gate fixes — catalog, budgets, knip
Regenerate config-catalog for the llm-replay paceMs row; condense the
testing.md web-lane entry to pointer form and raise its ceiling 1020->1060
(the two-sentence tier entry for a genuinely new surface does not fit the
old ceiling after relocation-first trims); internalize two harness helpers
knip flagged (rawSessionLog/normalizeAria are module-internal).
2026-07-24 20:30:33 +08:00
Tianyi Cui
2cd37e2765 docs(testing): web e2e lane docs + Agent Note to implemented
testing.md gains the web browser snapshot tier entry (divergent
DSH_SNAPSHOT=... test:web commands) and names apps/web/tests/snapshots/
as the web surface's snapshot home. The GUI testing note's tier map and
lane map gain the e2e scenarios (both languages, pair re-recorded) and
drop the stale verify-session-real references (those scripts left with
the missions/ tree). packages/client/AGENTS.md check ladder covers the
wire-carriage trigger and refresh/record commands. acp-snapshot README
stops claiming the whole package is ACP-specific — its normalizers are
transport-neutral with three consumers now. vitest.web.config.ts header
carries TODO(ci-browser) with the staged-reversal pointer.

The design-study Agent Note moves proposed/ -> implemented/ rewritten in
present tense: all review decisions recorded (llm:false seam over the
placeholder-key hack, providers-mode replay, whenIdle barrier stack,
single aria golden + anchors, TUI-style inline modes over a suite
factory, scrub-only header stance, CI deferral) with re-entry triggers
under Deferred.
2026-07-24 19:57:27 +08:00
Tianyi Cui
46b9a91e55 test(web): keyless browser e2e lane — replayed round trip + seeded cold resume
apps/web/tests/harness.ts boots the real web assembly in-process
(startHost llm:false -> installLlmReplay providers-mode -> mountWebPlugins
-> startWebServer) under DSH_SNAPSHOT replay/record/refresh. Barrier
stack: in-process turn/end -> agent.whenIdle (covers the persistence
flush) -> browser settled-poll. Seeding goes through the real persistence
API (semantic-checkpoint precedent); record harvests fixtures from live
session memory and tokenizes {{sessionId}}/{{cwd}}; refresh is the sole
golden writer. Console tripwires fail scenarios on reconnect/gap-repair
self-healing; harness close asserts full replay-fixture consumption.

Scenarios, each with fixtures recorded against THIS assembly via a live
model run: replay-round-trip (real composer -> real bash echo -> settled
markdown + aria golden + world-state event asserts) and seeded-history
(cold sidebar list -> implicit resume on open -> history tool cards from
the log, zero model calls). apps/web/tests are host-plane programs:
excluded from the client-registered apps/web project, included in
tsconfig.host.json (one program cannot hold both Context merge sides).
2026-07-24 19:49:02 +08:00
imccyu
e903a864f7 fix(gui): reconcile the rebase — ui-question joins the client roster
ui-question landed on master as a full dshClient plugin package (composer
question flow); it enters the flat roster, apps/cli deps/refs, and the
smoke graph. Restore the workspace-context and user-interaction host deps
the conflict resolution had dropped.
2026-07-24 02:09:42 +08:00
imccyu
b58f0989f9 refactor(gui): rebuild the client loading kernel as dsh-client-modules with a two-phase boot
The module system moves out of dsh-client-runtime (./loader retired) into
its own package: a lazy CJS table where executing a bundle only registers
its factory and materialization happens at first require, memoized, with
recursive requires self-ordering. ClientModuleSystem is a class; index.ts
keeps the types and a thin factory. Boot is two-phase: phase one prefetches
the immediately tier in parallel (registration only, failures deferred to
phase two's loud import); phase two mounts the vendored Loader with the
module system as internal, creates one entry per graph row plus the
app-shell pseudo-row the kernel appends itself, and settles on an
all-ACTIVE sweep. The shell kernel is self-sufficient: hand-rolled
loader-status stores, no plugin value imports, platform seed list single-
sourced in platform.ts.
2026-07-24 02:09:42 +08:00
imccyu
75d37654cb fix(gui): address review — stale smoke case, tooltip trigger overlap, collapse contract docs
- The real-browser collapse smoke tracked the old chrome: visible HARNESS
  text (the wordmark svg is aria-hidden now), an 'Expand sidebar' label
  (renamed 'Open sidebar'), a 300px settle (default is 280), and an
  immediate focus assert (rail search defers focus past the slide). The
  case now tracks the brand span, polls the deferred focus, and uses the
  current labels and width.
- Tooltip treated hover and focus as one trigger: leaving with the mouse
  dropped the bubble of a still-focused anchor (and vice versa). The two
  triggers are tracked independently; the bubble hides only after both
  clear. Spec pins both orders.
- The ui-sidebar README and the bilingual collapse note still described
  the retired geometry morph; both now state the slide + crossfade
  contract, the fixed-width (never-conceding) sidebar, and the rail's
  whale-mark/tooltip chrome.
2026-07-24 01:30:01 +08:00
Tianyi Cui
404da65595 Merge branch 'master' into worktree/web-session-titles
# Conflicts:
#	apps/web/tests/smoke-real.e2e.ts
#	packages/client/connection/tests/fixture.spec.ts
#	packages/host/runtime/README.md
#	packages/host/runtime/src/api-proxy.ts
2026-07-23 22:00:17 +08:00
Tianyi Cui
2a62da8ff0 test(web): make real title smoke deterministic 2026-07-23 20:41:10 +08:00
Tianyi Cui
d7c4822f95 Merge origin/master into worktree/web-session-titles
# Conflicts:
#	packages/client/connection/src/client/fixture.ts
2026-07-23 20:12:40 +08:00
imccyu
0f63666522 Merge branch 'worktree/web-carrier-chain' into worktree/web-ask-user-question 2026-07-23 20:09:11 +08:00
Yichen Jiang
0b0492e8ae test(web): align Markdown smoke with master 2026-07-23 19:23:35 +08:00
Yichen Jiang
6f624c67c4 feat(web): render assistant Markdown 2026-07-23 19:18:05 +08:00
imccyu
2b06aa0102 Merge branch 'worktree/web-carrier-chain' into worktree/web-ask-user-question
Restack the ask-user domain layer onto the carrier-chain architecture
branch. Conflict policy: runtime and ui-conversation take the
carrier-chain side (master sessions shape, dual-kind PendingCard,
'internal' envelope shell tests); the 'cancelled' wire code and its
semantics tests stay in apiproxy + ui-question (domain layer); the
smoke fixture keeps the nine-bundle success pass with the resident
question round over the carrier-chain first-describe shape.
2026-07-23 18:50:30 +08:00
Tianyi Cui
d2dc618e3f Merge remote-tracking branch 'origin/master' into worktree/web-session-titles
# Conflicts:
#	.agents/notes/implemented/process/2026-07-20-gui-testing-system.i18n.yaml
#	packages/client/ui-conversation/tests/apply-inject.spec.tsx
#	packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx
#	packages/client/ui-conversation/tests/gate-branch-tails.spec.tsx
#	packages/client/ui-conversation/tests/selection-survival.spec.ts
#	packages/client/ui-conversation/tests/skeleton-branches.spec.tsx
#	packages/client/ui-conversation/tests/skeleton.spec.tsx
#	packages/client/ui-layout/tests/service.spec.ts
#	packages/client/ui-sidebar/tests/apply.spec.tsx
#	packages/client/ui-sidebar/tests/store.spec.ts
#	packages/client/ui-trajectory/tests/views.spec.tsx
#	packages/client/web/src/app.tsx
#	packages/client/web/tests/boot.spec.tsx
#	packages/host/runtime/README.md
#	packages/host/runtime/tests/host-runtime.spec.ts
2026-07-23 18:39:48 +08:00
Tianyi Cui
f666c39379 fix(web): generate model-backed session titles 2026-07-23 18:20:17 +08:00
imccyu
62b62ac9d5 Merge remote-tracking branch 'origin/master' into codex/gui-context-polish
# Conflicts:
#	apps/web/tests/smoke-fixture.e2e.ts
2026-07-23 15:03:26 +08:00
Dudu-0223
0301b7fda8 feat(gui): add write tool row variant 2026-07-23 14:46:07 +08:00
Dudu-0223
aee859841b test(gui): cover edit tool row 2026-07-23 14:38:27 +08:00
Dudu-0223
1ba00bf80c feat(web): inject workspace instructions 2026-07-23 14:11:25 +08:00
Dudu-0223
edb999fd0f test(gui): cover thinking row disclosure 2026-07-23 14:11:25 +08:00
imccyu
698f46b0c9 feat(gui): morph the sidebar collapse instead of swapping renders
The collapse read as a hard cut: only the track width animated while the
panel content swapped instantly and the rail icons bore no relation to the
expanded layout. Now the four control rows persist across the transition —
collapse toggle, new session, new workspace, search, the same top-down
order as their expanded rows — and morph their geometry (row heights,
paddings, margins, capsule borders) on the deepsuite curve, so each rail
icon is its expanded control converging onto the 56px axis. Wide-only
content (brand, labels, input, session tree) cross-fades over 200ms, stays
mounted while the collapse animates, and unmounts at the 300ms settle,
still dropping the sessions subscription. The search query moves up to the
root and survives the round trip; rail search focuses the surviving input
after expand instead of remount-autofocus.
2026-07-23 13:03:32 +08:00
imccyu
5da8e3b787 feat(gui): animate sidebar collapse and grow the rail control set
The collapsed rail becomes a 56px icon column (24px controls between 16px
paddings) carrying expand, new session, search, and new workspace — each
aligned with its expanded counterpart; rail search expands the sidebar and
focuses the search box. Collapse/expand now animates: the frame transitions
grid-template-columns (and the surviving handle its left) on the deepsuite
sider curve — --ds-ease-in-out over --ds-transition-duration-slow, supplied
by ui-theme's base sheet. Transitions pause during drags (data-dragging on
the frame, set for the whole gesture) and under prefers-reduced-motion.
2026-07-23 11:45:54 +08:00
imccyu
a6649fb545 test(web): drop the stale fail-loud boot smoke case
Since the store engine moved into the client runtime, the ui-layout bundle
externalizes @deepseek-ai/dsh-client-runtime/client. This case's two-row
manifest (ui-layout + a deliberately absent probe) no longer boots ui-layout
at all: the unseeded runtime specifier fails it first, serial loading stops,
and the probe never reaches the failure list, so the assertion times out on
a premise the engine migration retired.
2026-07-23 10:52:46 +08:00
imccyu
7e6f0128b5 fix(gui): keep sidebar controls when collapsed
A closed sidebar previously resolved to a zero-width grid track, clipping
the only toggle and the settings entry with no visible recovery; the closed
preference persisted across reloads, locking the sidebar shut.

- columns.ts maps the closed preference (width 0) to a fixed 60px
  SIDEBAR_COLLAPSED rail through every step of the concession solve;
  closed details still resolve to zero width.
- AppFrame derives data-sidebar-collapsed and the sidebar slot's collapsed
  owner prop from the persisted preference instead of the resolved track
  width, and drops the resize handle while collapsed.
- SidebarRoot reads the owner collapsed prop; the expanded-only body is a
  separate component that unmounts while collapsed (dropping its sessions
  subscription), leaving the expand toggle and Settings in the rail.
- The keyless web smoke gains the ui-sidebar bundle (six real bundles) and
  pins the 60px rail collapse/expand round through the assembled client.
2026-07-23 10:51:57 +08:00
Tianyi Cui
fc23a89208 fix(web): title sessions created by host 2026-07-23 00:40:42 +08:00
Yichen Jiang
f0a9bb1ccd Merge remote-tracking branch 'origin/master' into worktree/web-ask-user-question
# Conflicts:
#	apps/web/tests/smoke-fixture.e2e.ts
2026-07-22 23:48:53 +08:00
Tianyi Cui
a9ea193e31 feat(web): render durable session titles 2026-07-22 23:43:53 +08:00
Yichen Jiang
03889cee1a feat(gui): add ask-user question composer 2026-07-22 23:39:50 +08:00
Tianyi Cui
f967537177 Merge remote-tracking branch 'origin/master' into worktree/web-bind-address
# Conflicts:
#	apps/cli/src/bin.ts
2026-07-22 21:51:05 +08:00
Tianyi Cui
6d756fd4c9 test(web): add race-free loopback smoke 2026-07-22 21:35:32 +08:00
Tianyi Cui
b44f8539e6 Merge latest master into tui-staging-merge-1 2026-07-22 20:45:24 +08:00
Tianyi Cui
c949a52627 fix(web): default to loopback binding 2026-07-22 20:35:38 +08:00