diff options
| author | Adam Malczewski <[email protected]> | 2026-06-12 15:21:03 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-12 15:21:03 +0900 |
| commit | e6f6bd86eab07954d8f06e740659969c3dfecc7f (patch) | |
| tree | 0971fde464a28fd1ed8a5a6aaf6e8e7125c43878 | |
| parent | 5ef7cc2916c544a66d68805063b02290f24d9a25 (diff) | |
| download | dispatch-web-e6f6bd86eab07954d8f06e740659969c3dfecc7f.tar.gz dispatch-web-e6f6bd86eab07954d8f06e740659969c3dfecc7f.zip | |
docs: merge ORCHESTRATOR.md into AGENTS.md as a single-agent guide
One main agent does planning, contracts, and the feature writing — no summoning. Removes ORCHESTRATOR.md and the now-obsolete summoning artifacts (.dispatch/package-agent.md + .dispatch/rules/frontend-*.md, whose code rules are restated in AGENTS.md); keeps the contract mirrors + daisyui reference. Fixes dangling refs in README.md and backend-handoff.md.
| -rw-r--r-- | .dispatch/package-agent.md | 81 | ||||
| -rw-r--r-- | .dispatch/rules/frontend-inject-transport.md | 7 | ||||
| -rw-r--r-- | .dispatch/rules/frontend-interpreter-generic.md | 8 | ||||
| -rw-r--r-- | .dispatch/rules/frontend-no-ambient-state.md | 7 | ||||
| -rw-r--r-- | .dispatch/rules/frontend-pure-core.md | 7 | ||||
| -rw-r--r-- | .dispatch/rules/frontend-styling.md | 12 | ||||
| -rw-r--r-- | AGENTS.md | 163 | ||||
| -rw-r--r-- | ORCHESTRATOR.md | 209 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | backend-handoff.md | 2 |
10 files changed, 122 insertions, 376 deletions
diff --git a/.dispatch/package-agent.md b/.dispatch/package-agent.md deleted file mode 100644 index 5c1a54d..0000000 --- a/.dispatch/package-agent.md +++ /dev/null @@ -1,81 +0,0 @@ -<!-- ORCHESTRATOR-ONLY meta (see ORCHESTRATOR.md): every FE summon is assembled as - package-agent.md + the inlined .dispatch/rules/* + the per-summon TASK block. - This is the base for ALL FE owner-agents; nothing here is restated per summon. --> - -# Frontend Owner-Agent — Brief - -You are the **sole owner-agent for exactly ONE unit** — a single feature module / -directory under `src/`. Your unit + job are in the **TASK** at the end. You build -it, test it, and write a report — nothing else. If no single unit is named, stop. - -## Hard guardrails (NON-NEGOTIABLE) -- **Single-writer, directory-scoped.** Read/create/edit any file inside your unit's - directory. Never create or edit anything OUTSIDE it — not another feature, not - `src/app/` (the composition root), not root config (`package.json`, - `tsconfig.json`, `vite.config.ts`, `biome.json`), not the harness, not the - backend repo (`../arch-rewrite`). -- **Need a change outside your unit?** Do NOT make it — write a CHANGE-REQUEST in - your report (a sibling's public export, a backend contract, root config, - composition wiring). The orchestrator dispatches it. -- **No workspace/dep wiring.** Don't `bun install` or edit root config; list a new - dep / wiring need as a CR. -- **No git** (no commits, branches, pushes, resets). - -## What you may read (visibility) -- **Your own unit:** every file, freely. -- **The contracts you consume:** reproduced IN-REPO under `.dispatch/*.reference.md` — read THOSE: - - `.dispatch/ui-contract.reference.md` — `@dispatch/ui-contract` (surfaces + surface WS protocol). - - `.dispatch/wire.reference.md` — `@dispatch/wire` (`Chunk`/`StoredChunk`+`seq`/`ChatMessage`/ - `AgentEvent`/`TurnSealedEvent`/`Usage` + metrics: `StepMetrics`/`TurnMetrics`, `usage.stepId`, - `step-complete`, `done.durationMs`/`done.usage`, `tool-result.durationMs` — the chat wire types). - - `.dispatch/transport-contract.reference.md` — `@dispatch/transport-contract` (HTTP endpoints + - `ChatRequest`/`ModelsResponse`/`ConversationHistoryResponse` + WS chat ops + the unified - `WsClientMessage`/`WsServerMessage` unions). - - Your code imports `@dispatch/ui-contract` / `@dispatch/wire` / `@dispatch/transport-contract` - normally, but **do NOT read `node_modules/@dispatch/*`** — they symlink to the backend repo - (OUTSIDE this repo) and a headless permission prompt will HANG the run (see "Headless read - boundary"). -- **Sibling units — PUBLIC SURFACE only:** their `index.ts` exports. Don't read - their internals (needing them ⇒ the contract is incomplete → report a CR). - -## Headless read boundary (you run non-interactively) -You run HEADLESS: a Read of any file OUTSIDE this repo (`dispatch-web/`) triggers a -permission prompt that CANNOT be answered → the run HANGS until aborted. Use Read/Edit ONLY -within `dispatch-web/`. If you believe you need a file outside your scope, do NOT attempt -the read — STOP and write the need in your report, then end. - -## Engineering standard (the inlined `.dispatch/rules/*` govern; in brief) -- **Pure core / injected shell.** Decision logic is `input → output`: zero DOM, - zero `fetch`/WS, zero Svelte. Put it in a `.ts` module (e.g. `logic/`) that tests - with NO mounting and NO mocks. Effects are INJECTED (props or an `adapter/`). -- **Svelte-thin.** `.svelte` files wire props/events to pure logic + render; no - business logic. (biome lints `.ts`/`.js` only; `svelte-check` owns `.svelte`.) -- **No ambient state.** Own state explicitly; runes wrap the pure reducer; - subscriptions are disposed on unmount. -- **Tests, asymmetric.** Pure logic → vitest with ZERO internal mocks (never - `vi.mock` of our own modules). Components → a few `@testing-library/svelte` - tests; don't chase coverage there, don't mock siblings. Faking the OUTERMOST - edge (a fake socket/fetch/clock) is the only allowed mock. -- **Isolation over DRY.** Self-contained over a shared helper wired between - features. The only shared surfaces are the imported contracts. -- **Strict TS:** respect `exactOptionalPropertyTypes` + `noUncheckedIndexedAccess`. - -## Verify before finishing — YOUR UNIT IN ISOLATION -Sibling units may be built IN PARALLEL, so do NOT judge yourself by the whole project. -`svelte-check` is whole-project: if it reports errors in files OUTSIDE your unit's directory, -that is concurrent work-in-progress — IGNORE it and ensure only YOUR files (plus any existing -consumer you must not break) are error-free. The SCOPED checks below are your authoritative -signals. Run them, and paste the output into your report: -- `bunx svelte-check --tsconfig ./tsconfig.json` → 0 errors in YOUR files -- `bunx vitest run src/<your-dir>` → all pass (count goes up) -- `bunx biome check src/<your-dir>` → clean -The orchestrator runs the authoritative full `typecheck`/`test`/`check`/`build`. - -## Report (REQUIRED) → `reports/<your-unit>.md` -1. Files created/changed. 2. Public surface you expose (exported types/functions/ -components). 3. New test names + the isolated-verify output. 4. Change-requests -(sibling export, backend contract, root config, composition wiring) — explicit and -actionable. - -Your specific **TASK** follows at the end of this prompt. diff --git a/.dispatch/rules/frontend-inject-transport.md b/.dispatch/rules/frontend-inject-transport.md deleted file mode 100644 index fbe83d7..0000000 --- a/.dispatch/rules/frontend-inject-transport.md +++ /dev/null @@ -1,7 +0,0 @@ -# Rule: inject the transport; parsers are pure - -The WS/NDJSON framing + parsing is a PURE function (bytes/messages → typed events); -the socket/fetch is INJECTED. Test the parser with crafted chunk inputs (and -trace-replay-style fixtures), never a live connection. The op-protocol core is a -pure state machine: `reduce(intent, incoming) → { viewModel, outgoingCommands }`; -the carrier (WebSocket) is the injected shell. diff --git a/.dispatch/rules/frontend-interpreter-generic.md b/.dispatch/rules/frontend-interpreter-generic.md deleted file mode 100644 index 557f2d4..0000000 --- a/.dispatch/rules/frontend-interpreter-generic.md +++ /dev/null @@ -1,8 +0,0 @@ -# Rule: the surface interpreter is generic - -The surface interpreter switches on field KINDS (toggle/progress/selector/stat/ -button/custom), NEVER on a surface id. An `if (surface.id === "...")` imports a -feature's identity into the platform and breaks isolation (guardrail 1). An -unknown field `kind` or a `custom` `rendererId` with no registered renderer → -GRACEFUL SKIP, never a crash. Render from the spec; the backend owns what a -surface contains. diff --git a/.dispatch/rules/frontend-no-ambient-state.md b/.dispatch/rules/frontend-no-ambient-state.md deleted file mode 100644 index 663bf1a..0000000 --- a/.dispatch/rules/frontend-no-ambient-state.md +++ /dev/null @@ -1,7 +0,0 @@ -# Rule: no ambient state (frontend) - -State is owned per-unit and passed explicitly. NO module-global mutable store -reached from everywhere — that is the old FE's "tools leak across tabs" / -"model resets on tab switch" bug class. Svelte runes (`$state`) are a THIN -reactive wrapper over a pure reducer, never the home of logic. Subscriptions are -owned and disposed on unmount (no orphaned or duplicate subscriptions). diff --git a/.dispatch/rules/frontend-pure-core.md b/.dispatch/rules/frontend-pure-core.md deleted file mode 100644 index fa7bc2e..0000000 --- a/.dispatch/rules/frontend-pure-core.md +++ /dev/null @@ -1,7 +0,0 @@ -# Rule: pure core / injected shell (frontend) - -Decision logic — reducers, view-models, formatters, parsers — is pure -(input → output): NO DOM, NO `fetch`/WebSocket, NO Svelte import. Put it in a -`.ts` module that tests with zero mounting and zero mocks. Effects (socket, fetch, -IndexedDB, clock) are INJECTED at the edges (props or an adapter). This is for -testability, not purity dogma — stop where it would only add ceremony. diff --git a/.dispatch/rules/frontend-styling.md b/.dispatch/rules/frontend-styling.md deleted file mode 100644 index e123619..0000000 --- a/.dispatch/rules/frontend-styling.md +++ /dev/null @@ -1,12 +0,0 @@ -# Rule: styling is DaisyUI v5 + the dracula theme (UI units only) - -The global stylesheet already enables **DaisyUI v5** with the **dracula** theme -(`data-theme` on `<html>`). Do NOT add or change theme config, re-import Tailwind/DaisyUI, -or hand-roll a design system — just apply DaisyUI / Tailwind utility classes in your -`.svelte` files (e.g. `select`, `textarea`, `btn btn-primary`, `tabs`, `chat chat-start` / -`chat-end` + `chat-bubble`). - -Keep components THIN: a `.svelte` file wires props/events to pure logic and applies classes -— it holds NO business logic (that stays in `logic/` / the reducer). Render plain semantic -HTML decorated with classes. biome lints `.ts`/`.js` only; `.svelte` correctness is -`svelte-check`'s job. @@ -1,18 +1,25 @@ -# Dispatch Web — Constitution (root AGENTS.md) +# Dispatch Web — Agent Guide (root AGENTS.md) -> Loaded every session. Non-obvious, project-specific rules only. If a fresh -> frontier model could infer it from the code, it is NOT here (P6). +> Loaded every session — the single source of truth for working in this repo: the +> build constitution (code rules) + the workflow. Non-obvious, project-specific rules +> only — if a fresh frontier model could infer it from the code, it is NOT here (P6). > Full design + rationale: `../arch-rewrite/notes/frontend-design.md` (and the > backend's `notes/restructure-plan.md` §1 for P1–P8). +> +> **You are the single agent for this repo.** You plan, author the cross-unit +> contracts/types, write the feature code, verify, keep the build green, and keep the +> living backend handoff current. There is no separate orchestrator and no summoned +> sub-agents — you do all of it yourself. ## What this is The **web frontend** for Dispatch — a SEPARATE repo from the backend (`../arch-rewrite`). It is a **thin shell + pure feature libraries + a surface host**, NOT a default-SvelteKit ball of mud. It consumes the backend's typed -contracts (`@dispatch/ui-contract` + the wire types) over HTTP + a WebSocket. -There is **no mandatory "chat is the root"**: the app is a COMPOSITION of feature -modules + surfaces, assembled at the composition root; legitimate frontends may -compose without chat at all. +contracts (`@dispatch/ui-contract` + the wire types) over HTTP + a WebSocket. The app +is a COMPOSITION of feature modules + surfaces, assembled at the composition root +(`src/app/`); there is **no mandatory "chat is the root"** — legitimate frontends may +compose without chat at all. Built with the backend's methodology: pure core / inject +effects / no ambient state / typed contracts / asymmetric testing. ## Stack Bun + Vite + Svelte 5 (runes) + TypeScript (strict). Biome for lint/format @@ -21,54 +28,124 @@ Bun + Vite + Svelte 5 (runes) + TypeScript (strict). Biome for lint/format semantics — it flags template-used vars as unused). Vitest + `@testing-library/ svelte` for tests. +## Repo geography +``` +src/app/ composition root (imports + wires feature modules + surface host) +src/core/ PURE: transcript · cache · surfaces (interpreter) · protocol · wire +src/features/<unit>/ logic/ (pure) · ui/ (svelte) · adapter/ (effects) +src/adapters/ injected browser effects: WS client, fetch, IndexedDB, history +.dispatch/ mirrored backend contracts (*.reference.md) + rules/ +reports/ (gitignored) +``` +Backend (SEPARATE repo, contracts only): `../arch-rewrite` — consume +`@dispatch/ui-contract` (`file:` dep) + the wire types. Do NOT edit it. + ## The non-negotiable rules -- **Pure core / injected shell.** Decision logic (reducers, view-models, - formatters, parsers) is pure `input → output`: zero DOM, zero `fetch`/WebSocket, - zero Svelte import. Effects (WebSocket, fetch, IndexedDB, history, clipboard) - are INJECTED at the edges. State is a pure reducer; Svelte runes are a THIN - reactive wrapper over it, never the home of logic. -- **No ambient state.** State is owned per-unit and passed explicitly. No - module-global mutable store reached from everywhere — that is the old FE's - "tools leak across tabs" / "model resets on tab switch" bug class. -- **Components are thin.** A `.svelte` file wires props/events to pure logic and - renders; it holds no business logic. -- **One owner per unit.** Each feature module / file has exactly ONE editing - agent. To change another unit you report the need up — you do not edit it. -- **Contracts are the only cross-unit surface.** You see other units' public - exports (`index.ts`) and the imported `@dispatch/ui-contract` / wire types — - never their internals. Needing internals ⇒ the contract is incomplete; report it. -- **The surface interpreter is GENERIC.** It switches on field KINDS - (toggle/progress/...), NEVER on a surface id. No `if (surface.id === "...")` — - that imports a feature's identity and breaks isolation. -- **Typed coupling.** Cross-feature links are typed imports/callbacks; no - stringly-typed event bus. Discovery-by-id (surface catalog, subscribe) is - sanctioned DATA flow, not a code reference. +- **Pure core / injected shell.** Decision logic (reducers, view-models, formatters, + parsers) is pure `input → output`: zero DOM, zero `fetch`/WebSocket, zero Svelte + import. Effects (WebSocket, fetch, IndexedDB, history, clipboard) are INJECTED at the + edges. State is a pure reducer; Svelte runes are a THIN reactive wrapper over it, + never the home of logic. +- **No ambient state.** State is owned per-unit and passed explicitly. No module-global + mutable store reached from everywhere — that is the old FE's "tools leak across tabs" / + "model resets on tab switch" bug class. Subscriptions are owned and disposed on unmount. +- **Components are thin.** A `.svelte` file wires props/events to pure logic and renders; + it holds no business logic. +- **Contracts are the cross-unit surface.** Cross-unit dependencies go through a unit's + public exports (`index.ts`) + the imported `@dispatch/ui-contract` / wire types — not + another unit's internals. If you find yourself needing a sibling's internals, the + contract is incomplete: fix the boundary, don't reach in. +- **The surface interpreter is GENERIC.** It switches on field KINDS (toggle/progress/…), + NEVER on a surface id. No `if (surface.id === "…")` — that imports a feature's identity + and breaks isolation. An unknown field kind / unregistered `custom` renderer → graceful + skip, never a crash. +- **Typed coupling.** Cross-feature links are typed imports/callbacks; no stringly-typed + event bus. Discovery-by-id (surface catalog, subscribe) is sanctioned DATA flow, not a + code reference. +- biome covers `.ts`/`.js`; `.svelte` correctness is `svelte-check`'s job. + +## Workflow +1. Plan the change; split a big change into dependency-ordered steps — build the shared + contract/types first, then the producers, then the consumer / composition root. +2. Overlap/vocab check vs `GLOSSARY.md` before naming anything new — ask the user before + coining a term (see Vocabulary). +3. Author/extend the cross-unit seam (the shared type/port/`index.ts` export) before + writing the code that crosses it. +4. Write it: pure logic in `logic/` (zero DOM/fetch/Svelte), thin `.svelte` in `ui/`, + effects injected at the edges (`adapter/` or props). Tests alongside. +5. Verify yourself (below). +6. Commit the milestone; update progress + the living handoff (see Backend seam). + +## Verification (run these yourself — trust the green, not intent) +``` +bun run typecheck # svelte-check — 0 errors +bun run test # vitest — note the pass count +bun run check # biome (.ts/.js) — clean +bun run build # vite build — succeeds +git status --short # confirm you only touched what you meant to +``` +Asymmetric testing: pure logic → vitest with NO internal `vi.mock` of our own modules; +components → a few `@testing-library/svelte` tests (fake only the OUTERMOST edge — +socket/fetch/clock, never a sibling module). Re-run the suite TWICE when a change touches +effects backed by a shared global (`fake-indexeddb`, `localStorage`) to catch cross-test +pollution. After a slice that touches the wire or browser effects, run a LIVE probe (below). ## Backend seam (cross-repo) -The backend is `../arch-rewrite` (separate repo; `lsp references` does NOT span -the boundary). You consume `@dispatch/ui-contract` (surfaces) + the wire types as -a pinned dependency. Need a backend contract change? REPORT IT UP — the -orchestrator carries it across (couriered via the user). Never reach into the -backend repo. +The backend is `../arch-rewrite` (separate repo; `lsp references` does NOT span the +boundary). You consume `@dispatch/ui-contract` + the wire/transport types as pinned `file:` +deps. **Read the in-repo mirrors `.dispatch/*.reference.md`, never `node_modules/@dispatch/*`** +(they symlink out of the repo); regenerate the relevant mirror whenever a contract changes. +- **FE contract change** (a shared FE type / service handle): edit it, run `lsp references`, + and update every consumer yourself. +- **Backend contract change:** `lsp` does not span repos — REPORT IT UP via the living + handoff `backend-handoff.md` (repo root, tracked); the user couriers it to the backend and + brings the reply back. On the new version: re-pin the `file:` dep → re-mirror the relevant + `.dispatch/*.reference.md` → update FE consumers. NEVER edit the backend repo. + +Keep `backend-handoff.md` current at every milestone: FE slice status, pinned contract +versions + mirrors, open asks / roadblocks for the backend, findings, and likely next asks. ## Surfaces (the modular UI mechanism) -A **surface** is backend-declared, frontend-agnostic data (fields + values + -actions), rendered generically. See `frontend-design.md` §4. You render surfaces; -you never special-case a specific one. +A **surface** is backend-declared, frontend-agnostic data (fields + values + actions), +rendered generically. See `frontend-design.md` §4. You render surfaces; you never +special-case a specific one. + +## Live integration probe (effectful seams hide bugs unit tests can't) +Pure/unit green is necessary, NOT sufficient: a transport/storage SHELL only fails for real +against a running backend (the slice-1 WS-upgrade bug, and the secure-context +`crypto.randomUUID` blank page, only surfaced live). After a slice that touches the wire or +browser effects, run a LIVE probe: +- `scripts/live-probe.ts` (`bun scripts/live-probe.ts`) drives the REAL network-facing modules + (`adapters/ws` + `core/chunks` + cache + HTTP sync) against the running backend. Keep it OUT + of `bun run test` so a down backend never reds CI. It runs in Bun (global `WebSocket`/`fetch`); + use `fake-indexeddb/auto` for IndexedDB. +- The backend is the USER's process — never boot it yourself (headless boot+probe HANGS + leaks + servers, EADDRINUSE). Confirm reachability first (`curl :24203/health`), then CONNECT. Probe + with the REAL env (never an overridden empty key). +- Browser-only bugs (secure-context APIs, theme, layout) still need a human at the page — list + the exact things to click and ask the user to confirm. ## Commands - `bun run typecheck` — svelte-check - `bun run test` — vitest - `bun run check` — biome (`.ts`/`.js`) - `bun run build` — vite build +- `bun run dev` — Vite dev server (port 24204). Full stack: backend `bin/up` + (HTTP :24203, surface WS :24205) + FE Vite :24204. + +## Status +Slices 1–3 DONE + committed (surface system + WS; conversation transcript cache + delta +streaming; tabs + model selector + DaisyUI/dracula), plus per-conversation cwd + LSP view, +context size, cache-warming (+ retention/timer), markdown, smart auto-scroll, and +multi-client live view (subscribe/reconnect + the user prompt on the event stream). Plan in +`../arch-rewrite/notes/frontend-design.md` §10. ## Reports -Finish a task → write `reports/<your-unit>.md` (gitignored): what you built, the -public surface, test/typecheck/build output, and any contract gaps / change- -requests (incl. backend-contract CRs for the orchestrator to courier). +Optionally record a finished milestone in `reports/<name>.md` (gitignored): what you built, +the public surface, verification output, and any contract gaps / backend-contract CRs. ## Vocabulary -Use `GLOSSARY.md`. Shared backend terms are canonical (conversation, turn, step, -chunk, AgentEvent, model name, …). FE terms: surface, region, field kind, -action / action ref, surface catalog. **"view" is RESERVED** (old-Dispatch sidebar -UX, future). Never invent a synonym. +Use `GLOSSARY.md`. Shared backend terms are canonical (conversation, turn, step, chunk, +AgentEvent, model name, …). FE terms: surface, region, field kind, action / action ref, +surface catalog. **"view" is RESERVED** (old-Dispatch sidebar UX, future). Never invent a +synonym. diff --git a/ORCHESTRATOR.md b/ORCHESTRATOR.md deleted file mode 100644 index eb5e381..0000000 --- a/ORCHESTRATOR.md +++ /dev/null @@ -1,209 +0,0 @@ -# ORCHESTRATOR.md — driving dispatch-web (the frontend) - -> **You are the orchestrator for the frontend repo.** You do NOT write feature -> code. You plan, author contracts/config/harness, summon owner-agents (one per -> unit), verify, resolve, keep the build green. Read fully before acting. Also -> read: `AGENTS.md` (the FE constitution you enforce), `GLOSSARY.md`, -> `.dispatch/rules/`, and the design home `../arch-rewrite/notes/frontend-design.md` -> (+ `../arch-rewrite/notes/restructure-plan.md` §1 for P1–P8). This MIRRORS the -> backend's `../arch-rewrite/ORCHESTRATOR.md` — read that for the deep rationale. - -## 0. Mental model -The frontend is a **composition of feature modules + a surface host**, built with -the backend's methodology (pure core / inject effects / no ambient state / typed -contracts / one owner per unit / asymmetric testing). The team structure is -isomorphic to the module structure: agents communicate only through contracts. The -surface system (backend-declared, frontend-agnostic UI) is the modular UI -mechanism — see `frontend-design.md` §4. No feature (not even chat) is the -mandatory structural root. - -## 1. The golden workflow -1. Plan the unit(s); split into dependency-topological **waves** of disjoint units, and - WIDEN each wave where you can (§2a); one agent owns one unit. -2. Overlap/vocab check vs `GLOSSARY.md` before naming anything new (§5.6 — ask the - user before coining a term). -3. Pre-author the cross-unit seam (§3) + write the per-summon TASK to - `prompts/<unit>.md` (gitignored). RE-READ `.dispatch/rules/` + the §2 scoping map - before each wave — summon from the files, NOT from memory. -4. Summon a wave via `opencode run` (§2); disjoint units run in PARALLEL (§2a). -5. Verify the reports + independently re-run typecheck/test/check/build (§4). -6. Resolve contract gaps / CRs / agent failures (§5, §5a). -7. Commit the milestone; update progress + the living handoff (§10). - -## 2. Summoning agents (`opencode run`) -**Working dir:** the repo root `/home/tradam/projects/dispatch/dispatch-web` (so the -agent's `lsp` tool uses THIS repo's TS server). -**Model:** `opencode-go/mimo-v2.5-pro` for building. -**Invocation:** concatenate the brief + the scoped rules + the TASK; redirect output -to a log file; never use `-f`. -```bash -cd /home/tradam/projects/dispatch/dispatch-web && \ -opencode run --dir /home/tradam/projects/dispatch/dispatch-web \ - -m opencode-go/mimo-v2.5-pro \ - "$(cat .dispatch/package-agent.md) -$(cat .dispatch/rules/frontend-pure-core.md .dispatch/rules/frontend-no-ambient-state.md) - -## TASK -$(cat prompts/<unit>.md)" \ - > reports/<unit>.run.log 2>&1 -``` -**MANDATORY — capture output to a file, never display it** (the stream is huge and -will crash the harness). Read the agent's `reports/<unit>.md`; `grep`/`tail` the log -only for a specific error. -**Run discipline:** do NOT background (no shell `&`); large timeout (e.g. 1800000 ms). -Each summon is its own FOREGROUND `opencode run`. **Parallelism = emit several summon -calls in ONE assistant message (concurrent tool calls), one per unit — NOT shell -backgrounding and NOT `&`.** Parallel summons ONLY for disjoint file sets (single-writer); -see §2a. `AGENTS.md` is auto-loaded by opencode — never `cat` it. - -**GOTCHA — headless cross-repo read = HANG.** An agent's Read of any file OUTSIDE `--dir` -(here `dispatch-web/`) triggers a permission prompt that CANNOT be answered headlessly → the -run wedges until aborted. The `@dispatch/ui-contract` `file:` dep symlinks OUT of this repo, so -reading `node_modules/@dispatch/*` hangs. Mitigation (in place): the contract is mirrored in-repo -under `.dispatch/*.reference.md` — one per consumed backend contract (`ui-contract`, `wire`, -`transport-contract`) — agents read THOSE; the brief forbids `node_modules/@dispatch/*` reads. -**Regenerate the relevant snapshot whenever that contract changes** (re-pin → re-mirror → fan out -consumers), and point `package-agent.md` at every mirror. Agents are told: if you'd need a file -outside your scope, report it and STOP — never attempt the read. - -### `.dispatch/rules/` scoping map (inline ONLY the matching rows) -- **Every FE agent:** `frontend-pure-core.md`, `frontend-no-ambient-state.md`. -- **Surface interpreter / renderer / field-component unit:** + `frontend-interpreter-generic.md`. -- **Transport / protocol / WS-client unit:** + `frontend-inject-transport.md`. -- **Any unit that builds `.svelte` UI (app shell, chat view/composer, surface field components):** + `frontend-styling.md`. - -## 2a. Parallel execution — WAVES -Throughput comes from running disjoint units at once. Organise it as waves: -- **A wave = a set of units that (a) touch DISJOINT files and (b) have no compile-time - dependency on each other** (each imports only already-built units + the pinned contracts). - Launch a wave by emitting one summon `Bash` call per unit IN A SINGLE MESSAGE (§2). Later - waves depend on earlier ones; the composition root (`src/app/`) is almost always the LAST wave. -- **Widen waves deliberately.** Before summoning, look for dependency edges you can remove so a - unit moves into an earlier (wider) wave — e.g. make an adapter GENERIC so it doesn't import a - feature's port (a `LocalStore<T>` instead of a tabs-specific store), or have the consumer define - the port it needs so the producer/consumer split disappears. Fewer edges ⇒ wider waves ⇒ faster. -- **One writer per file, always** — even across waves. If two units would edit the same file, they - are NOT separable; merge them into one unit or sequence them. -- **After a wave:** read every report, run the §4 checks ONCE for the whole wave (not per unit), - commit the milestone, then start the next wave. Don't interleave a new wave before the prior one - is green. - -## 3. The per-summon `prompts/<unit>.md` is JUST the TASK -The invariant guardrails live in `package-agent.md` + the inlined rules. The TASK -states only the non-inferable, project-specific job: your unit's directory; the job -+ algorithm naming the contract types involved; the contract file(s) to read -(`.dispatch/*.reference.md`, a sibling's `index.ts`); the required named test cases. - -**Pre-author the cross-unit seam.** When two units in the SAME wave must interoperate (a producer -+ a consumer that never see each other's internals), the orchestrator pins the shared interface in -BOTH prompts before summoning: name the exact port/type, say who DEFINES it and who IMPORTS it -(consumer-defines-port is the default — the adapter implements it), and which `index.ts` to import -from. That precise seam is what lets disjoint, blind units compose on the first try. - -**Tell each agent it has company.** Add the concurrency note to every wave prompt: sibling units are -being built in OTHER dirs right now; `svelte-check`/biome are whole-project, so if a check reports -errors OUTSIDE your unit's dir, that's concurrent WIP — ignore it and ensure YOUR files are clean. -The orchestrator's post-wave run (§4) is the source of truth. - -**Make agents IMPLEMENT, not deliberate.** A summoned owner must edit files + run its checks + write -its report in the one run. Prompts should say so explicitly when needed (see §5a). - -## 4. Verification (re-run yourself — trust nothing) -```bash -cd /home/tradam/projects/dispatch/dispatch-web -bun run typecheck # svelte-check — 0 errors -bun run test # vitest — note the pass count -bun run check # biome (.ts/.js) — clean -bun run build # vite build — succeeds -git status --short # confirm the agent stayed in its lane -``` -Trust = contracts + public surfaces + green checks + the report — NOT reading impl. -For pure units, confirm tests use NO internal `vi.mock` of our modules. - -**Concurrency caveat:** because `svelte-check`/biome/`vitest` are whole-project, an agent's OWN -verification (mid-wave) can transiently see a sibling's half-written file. Don't act on an agent -report's out-of-dir errors; YOUR post-wave run is authoritative. **Run the suite TWICE** when a wave -touched effects backed by a shared global (e.g. `fake-indexeddb`, `localStorage`) — to catch -cross-test pollution / flakiness before committing. - -## 5. Errors, CRs, cross-repo -- **FE contract change** (a shared FE type / service handle): the owner edits it, - runs `lsp references`, reports the consumer list; the orchestrator dispatches the - fan-out. -- **CR for build/config** (root tsconfig/vite/biome/package.json): the orchestrator - edits directly. **CR for impl** (a sibling, composition wiring in `src/app/`): the - orchestrator SUMMONS the owning agent. -- **BACKEND contract change (cross-repo):** `lsp references` does NOT span the two - repos. The FE pins `@dispatch/ui-contract` + wire types as a dependency. A needed - backend change is reported UP and **couriered by the user** to the backend - orchestrator (via the living handoff, §10); on the new version, re-pin + re-mirror + - fan out FE consumers. NEVER edit the backend repo. - -## 5a. Agent-failure recovery patterns -- **Plan-only / "shall I proceed?" agent.** A summon sometimes returns a PLAN and stops without - editing (no diff, no report). Detected via `git status` + a missing `reports/<unit>.md`. Re-summon - the SAME TASK prefixed with: "IMPLEMENT THIS NOW — make all edits, run the checks, write the - report; do not stop to plan or ask." Don't hand-fix its work. -- **Behaviour change reds a SIBLING's tests (test fan-out).** When a unit's new behaviour invalidates - another unit's test assertions, the failing tests belong to that OTHER owner — summon it with a - focused "fix these N failing tests to match the new behaviour" TASK (state the behaviour). The - orchestrator does not edit feature tests itself. -- **Agent strayed out of its lane.** `git status` after every wave; if an agent touched a file - outside its dir, decide per §5/§6: keep it if it's legitimately the orchestrator's lane (build/ - config/harness) and note it; otherwise revert + re-summon with a tighter scope. (Seen: an `app` - agent edited root `vitest-setup.ts` instead of filing a CR — adopted because it's config, but it - should have been a CR.) -- **Flaky green.** If a wave's suite passes once but the units use shared-global effects, re-run - before trusting (see §4). - -## 6. Restrictions (NEVER violate) -- Single-writer: never two agents on one file. -- The orchestrator never reads/edits feature impl (`.ts`/`.svelte`). It MAY edit: - (a) locally-mirrored/consumed contract pins, (b) build/config (tsconfig, vite, - biome, package.json, .gitignore), (c) harness/docs (this file, AGENTS.md, - GLOSSARY.md, `.dispatch/`, prompts/, reports/). The composition root (`src/app/`) - changes ONLY via a summoned owner. Roadblock → ask the user. -- The surface interpreter is GENERIC (no surface-id special-casing). -- biome covers `.ts`/`.js`; `.svelte` correctness is `svelte-check`'s. - -## 7. Repo geography -``` -/home/tradam/projects/dispatch/dispatch-web (THIS repo) - AGENTS.md ORCHESTRATOR.md GLOSSARY.md - .dispatch/{package-agent.md, rules/frontend-*.md} - src/app/ composition root (imports + wires feature modules + surface host) - src/core/ PURE: transcript · cache · surfaces (interpreter) · protocol · wire - src/features/<unit>/ logic/ (pure) · ui/ (svelte) · adapter/ (effects) - src/adapters/ injected browser effects: WS client, fetch, IndexedDB, history - prompts/ (gitignored) reports/ (gitignored) -``` -Backend (SEPARATE repo, contracts only): `/home/tradam/projects/dispatch/arch-rewrite` -— consume `@dispatch/ui-contract` (`file:` dep) + the wire types. Do NOT edit it. - -## 8. Status -Slices 1–3 DONE + committed (surface system + WS; conversation transcript cache + delta streaming, -live-verified 9/9; tabs + model selector + DaisyUI/dracula). Plan in -`../arch-rewrite/notes/frontend-design.md` §10. Dev server: `bun run dev` (port 24204); full stack: -backend `bin/up` (HTTP :24203, surface WS :24205) + FE Vite :24204. - -## 9. Live integration probe (effectful seams hide bugs unit tests can't) -Pure/unit green is necessary, NOT sufficient: a transport/storage SHELL only fails for real against a -running backend (the slice-1 WS-upgrade bug, and the secure-context `crypto.randomUUID` blank page, -only surfaced live). After a slice that touches the wire or browser effects, run a LIVE probe: -- A gated `scripts/live-probe.ts` (run with `bun scripts/live-probe.ts`) that drives the FE's REAL - network-facing modules (`adapters/ws` + `core/chunks` + cache + HTTP sync) against `bin/up`. Keep it - OUT of `bun run test` so a down backend never reds CI. It runs in Bun (global `WebSocket`/`fetch`); - use `fake-indexeddb/auto` for IndexedDB. -- The backend is the USER's process (never boot it yourself — headless boot+probe HANGS + leaks - servers, EADDRINUSE). Just CONNECT to the open ports; confirm reachability first (`curl :24203/ - health`). Probe with the REAL env (never an overridden empty key). -- Browser-only bugs (secure-context APIs, theme, layout) still need a human at the page — list the - exact things to click and ask the user to confirm. - -## 10. Living cross-repo handoff — `backend-handoff.md` -`lsp` can't span repos, so the FE↔backend seam flows through ONE rolling doc, `backend-handoff.md` -(repo root, tracked), kept current so the user can courier the whole seam at any time. It records: FE -slice status; the pinned contract versions + mirrors; OPEN asks / roadblocks for the backend; -findings (e.g. "model is per-turn, not persisted per conversation"); and likely NEXT asks. Update it -at every milestone and whenever a backend need arises; the user carries it across and brings the -reply back (e.g. `backend-handoff-reply.md`). @@ -100,4 +100,4 @@ bun run check # biome (.ts/.js; .svelte correctness is svelte-check's job ## Documentation - **Design + plan:** `../arch-rewrite/notes/frontend-design.md` -- **Build rules:** `AGENTS.md` · **Orchestration:** `ORCHESTRATOR.md` · **Vocabulary:** `GLOSSARY.md` +- **Build rules + workflow:** `AGENTS.md` · **Vocabulary:** `GLOSSARY.md` diff --git a/backend-handoff.md b/backend-handoff.md index b784eef..b5cf1eb 100644 --- a/backend-handoff.md +++ b/backend-handoff.md @@ -3,7 +3,7 @@ > **Purpose:** the single rolling document the FE orchestrator keeps current so the user can hand off > the whole FE↔backend seam at any time — on completion OR at a roadblock. Updated continuously. > **From:** dispatch-web orchestrator · **To:** arch-rewrite orchestrator · **Courier:** the user. -> `lsp` does NOT span the repos (ORCHESTRATOR §5) — every cross-repo ask flows through here. +> `lsp` does NOT span the repos (AGENTS.md § Backend seam) — every cross-repo ask flows through here. _Last updated: 2026-06-12. **FE is current on `[email protected]` / `[email protected]`.** All handoffs to date are consumed: surfaces + WS, conversation transcript/metrics, tabs + model selector, |
