diff --git a/apps/web/TESTING_INTERACTION_LEDGER.md b/apps/web/TESTING_INTERACTION_LEDGER.md new file mode 100644 index 00000000000..7b9c5d06e42 --- /dev/null +++ b/apps/web/TESTING_INTERACTION_LEDGER.md @@ -0,0 +1,50 @@ +# Web Interaction Ledger + +This ledger maps every high-value user interaction to concrete automated tests. +Rows are written as behavior invariants (what must stay true), not implementation +details (how it is coded). + +## Coverage Principles + +- Prefer threat/invariant tests over shape/snapshot tests. +- Cover interaction boundaries where regressions are expensive: + - stream/event handling, + - run lifecycle transitions, + - workspace/object state synchronization, + - destructive file operations and path safety. +- Keep tests deterministic: avoid network dependencies, isolate state per test. + +## Interaction Matrix + +| Area | Interaction | Invariant Protected | Test Owner / File | +| --- | --- | --- | --- | +| Chat stream parsing | `user-message` boundary in SSE stream | New user turn always resets streaming accumulators and starts a clean message segment | `app/components/chat-panel.stream-parser.test.ts` (new) | +| Chat stream parsing | Reasoning start/delta/end events | Reasoning text is contiguous and reasoning state is closed on end | `app/components/chat-panel.stream-parser.test.ts` (new) | +| Chat stream parsing | Tool input/output/error events | Tool call state transitions are monotonic (`input -> output|error`) per `toolCallId` | `app/components/chat-panel.stream-parser.test.ts` (new) | +| Chat stream parsing | Partial/unknown events | Parser ignores unrecognized events without corrupting prior parsed parts | `app/components/chat-panel.stream-parser.test.ts` (new) | +| Chat runtime | Initial send, queue, and stop behavior | A running session cannot start overlapping runs; queued messages are preserved until resumed | `lib/active-runs.test.ts` (existing) + `app/api/chat/chat.test.ts` (existing) | +| Chat runtime | Stream replay after reconnect | Replay emits buffered events in order and does not duplicate already-seen global sequence events | `lib/active-runs.test.ts` (existing) | +| Chat runtime | Silent token filtering | `NO_REPLY` full and partial token leaks never appear in final text | `lib/active-runs.test.ts` (existing, add cases) | +| Chat message rendering | Tool and report block grouping | Mixed tool/report/diff parts render in stable groups without dropping order | `app/components/chat-message.test.tsx` (new) | +| Chat message rendering | Tool status badges | Tool states reflect terminal result (`success` vs `error`) consistently | `app/components/chat-message.test.tsx` (new) | +| Subagent lifecycle | Registration + independent stream | Subagents continue streaming independently of parent run closure | `lib/subagent-runs.test.ts` (existing) + `lib/active-runs.test.ts` (existing) | +| Subagent lifecycle | Follow-up messaging | User follow-up persists before spawn and stream resumes from same subagent session key | `lib/subagent-runs.test.ts` (existing, add cases) | +| Subagent lifecycle | Abort from panel | Abort transitions run to non-running immediately and unblocks next action | `lib/subagent-runs.test.ts` (existing, add cases) | +| Workspace tree | Browse/fetch races | Stale fetch responses do not overwrite newer tree state | `app/hooks/use-workspace-watcher.test.ts` (new) | +| Workspace tree | Context actions (rename/delete/mkdir) | File operations reject traversal/system-file violations and apply only under workspace root | `app/api/workspace/file-ops.test.ts` (existing) | +| Workspace tree | Search/filter in sidebar | Search result selection opens the exact target and does not mutate unrelated open branches | `app/components/workspace/workspace-sidebar.test.tsx` (new) | +| Workspace routing | URL-driven state (`path`, `entry`, `chat`, `send`) | Query params hydrate expected panel state exactly once per route change | `app/workspace/page.url-state.test.tsx` (new) | +| Object views | Active view from `.object.yaml` on first load | Active view display and actual table filter/query state are consistent on initial render | `app/workspace/page.object-view-sync.test.tsx` (new) | +| Object views | Active view refresh via SSE update | When server updates active view, table/filter/column state atomically follows server value | `app/workspace/page.object-view-sync.test.tsx` (new) | +| Object views | Manual load/save/toggle view | Loading/saving/toggling views never desynchronizes selected view label from filters | `app/workspace/page.object-view-sync.test.tsx` (new) | +| Object table | Pagination, search, sort boundaries | Pagination/search/sort query composition is stable and server page resets when required | `app/workspace/page.object-view-sync.test.tsx` (new) + `app/api/workspace/objects.test.ts` (existing) | +| Reports | Multi-panel execution | Partial panel failures are isolated; successful panels still render | `app/components/charts/report-viewer.test.tsx` (new) | +| Profile lockdown | Single profile UX and API lock | Web profile switch/init surfaces remain immutable (403) and UI does not expose forbidden actions | `app/api/profiles/route.test.ts` (existing), `app/api/workspace/init/route.test.ts` (existing) | + +## Completion Criteria + +- Every row above has either an existing passing test or a new passing test in + the mapped file. +- High-risk rows (chat stream, subagent lifecycle, object view sync) include at + least one failure-mode regression test. +- Added tests are deterministic and repeatable in CI and local runs.