Skip to content

Testing plan: a control-plane dashboard over /api/v1

Derived from requirements.md and design.md. Authored at test-planning, completed at verification.

Test matrix

#TypeApplies?Scope / what it provesWhere it runs
T1UnityesThe join in model.ts: ref parsing incl. GHE hosts, spec-id derivation, positional rail reads, the four-record union, prRepo qualification, the inbox union, relative timescd ui && bun run test src/api/model.test.ts
T2UnityesThe transport in client.ts: refs travel as query parameters, list parameters repeat, graph/check posts prRepo defaulted, a cross-origin TypeError becomes kind: "network" with the tunnel advice, FastAPI's detail survives a 4xx, a non-JSON error body falls back to the status linecd ui && bun run test src/api/client.test.ts
T3Contract (OpenAPI)partial — see noteThe client's routes, methods and body keys are written against docs/api-specs/openapi/the-loop.v1.yaml, and T2 pins them. A generated-client parity test is not added: the contract types most responses as additionalProperties: true, so generation would produce Record<string, unknown> and prove nothing the hand-written types do notcd ui && bun run test src/api/client.test.ts
T4End-to-endyes (component-level)The whole React layer against the demo transport: the board lists all eight items with positions, the parked item is flagged, a row opens its detail with both PRs on their own inner loops, the attention tab shows the union, a deep link to an unknown ref degradescd ui && bun run test src/App.test.tsx
T5UI / visualmanualThe rendered result against the approved design: tokens, blueprint frames, node rails, table rhythm. No screenshot baseline — the design system is vendored verbatim, so a pixel baseline would test the CSS export rather than this appreviewer, bun run dev
T6Snapshotn/a — the interesting output is the join, asserted structurally in T1; a DOM snapshot would fail on every copy edit and prove nothing about behaviour
T7Performance / loadyes (by construction, asserted in review)NFR1: the graph round is a four-worker pool over N jobs, and the flat lists render before it starts. Pinned by reading GRAPH_CONCURRENCY and the two setViews calls rather than by a timing test, which would be flaky and machine-dependentcode review of useControlPlane.ts
T8Security / abuse caseyesThe five abuse cases of requirements.md: the posture note is present and no path proposes service.exposed: true; no dangerouslySetInnerHTML/eval/dynamic import anywhere in src/; the base URL is always visible; corrupt localStorage degrades field by field; demo mode banners on every screencd ui && bun run test src/state/settings.test.ts + the grep in T8b
T8bSecurity / staticyesgrep -rn "dangerouslySetInnerHTML|eval(|new Function|innerHTML" ui/src returns nothingCI (bun run lint config forbids none of these directly — run as a review step)
T9Accessibilityyes (partial)NFR4: every control is a real button/a/input with an accessible name — T4 selects only by role and accessible name, so a control that lost its name fails the suite. The pulse is suppressed under prefers-reduced-motion by a media query. A full audit (contrast, focus order under assistive tech) is not in this passcd ui && bun run test src/App.test.tsx
T10Migration / upgradeyes (as a non-migration)Nothing existing changes shape: no schema, no record, no route. The settings store is versioned (…:v1) from birth, and an absent store is the default pathT1/T8
T11Manual exploratoryyesThe one thing no automated test here covers: the app against a real service. Start the-loop service start, run bun run dev, confirm the board, a graph rail, a pause/resume round trip and the event logoperator, before merge

On T3. The strongest available contract check is that the parity test already in the repo (cli/tests/test_api_contract_parity.py) keeps the served schema equal to the authored one; this client is written against the same file. If a future work item gives the API real response models, generating types from the contract becomes worthwhile and should replace types.ts.

Scenarios & requirement trace

RowRequirement(s)Scenario / case
T1R1.1a work item with no session, and a session with no portable record, both survive the union
T1R1.2the report's currentNode and progress reach the row
T1R1.3with no report, the rail comes from the record's frozen nodes and the row still exists
T1R1.5a parked gate outranks a paused session in the row flag; a healthy item has no flag
T1R2.1passed / current / skipped / blocked are distinguished; a blocked pointer is not drawn as current
T1R2.3prRepo is "" for a same-repo PR and owner/repo for one elsewhere
T1R2.4the Claude Code transcript path is derived from cwd + session id; cursor yields null
T1R4.1, R4.2, R4.3the inbox is /attention ∪ parked gates, urgent first
T2R3.1, R3.2the control and gate calls hit the documented routes with the documented bodies
T2R3.3the service's detail is what the operator is shown
T2R5.4a cross-origin TypeError produces the tunnel/gateway advice
T4R1.1, R1.2, R1.5eight items listed, positions arrive, the parked one is flagged
T4R2.1, R2.2the detail screen shows the outer loop and one card per PR
T4R2.5the reply box is present, disabled, and its title names POST /api/v1/sessions/reply; the trace says outright that turns and tool calls are not served
T4R5.6a deep link to a ref this service does not have degrades to a message, not a crash
T8R5.5 / abuse 5demo mode banners; verbs stay in memory
T8abuse 4a store holding {not json, a bad mode, a string pollSeconds, an absurd interval
T8R5.2, R5.3the base URL round-trips through storage and is normalized
T11R1.2, R3.1, R3.4against a real service: positions render, a pause round-trips, the board re-fetches

Coverage gaps accepted in this pass

Stated rather than left implicit:

  • No test drives a real service. T11 is manual. An integration test would need a running daemon, a checkout and a tmux session in CI; the seam is instead pinned at the HTTP boundary (T2) against the authored contract.
  • No visual baseline (T5) and no full accessibility audit (T9).
  • NFR1 is reviewed, not measured (T7).

Released under the MIT License.