summaryrefslogtreecommitdiffhomepage
path: root/backend-handoff.md
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-12 00:22:42 +0900
committerAdam Malczewski <[email protected]>2026-06-12 00:22:42 +0900
commit4866ebe724d247ab9c1a7615104712cf0ff2e508 (patch)
tree67d178201c50dca60e81091375988ea582140f47 /backend-handoff.md
parentdb86285444aab921663eb1dfce5c84c554204c94 (diff)
downloaddispatch-web-4866ebe724d247ab9c1a7615104712cf0ff2e508.tar.gz
dispatch-web-4866ebe724d247ab9c1a7615104712cf0ff2e508.zip
feat(workspace,smart-scroll): per-conversation cwd + LSP view; smart auto-scroll
workspace ([email protected]): a cwd field in the Model sidebar view (GET/PUT /conversations/:id/cwd) + a new 'Language Servers' view (GET /conversations/:id/lsp) with per-server connected/starting/error badges, spinner, error text, and refresh. Store-owned reactive cwd, re-seeded on focus change; works for DRAFTS too (targets the draft's client-minted id, which survives promotion, so turn 1 runs in the chosen cwd). Network seam normalizes the untyped LSP body. smart-scroll: pure stick-to-bottom reducer + injected controller shell (scroll/scrollend + a ResizeObserver on the content so the view follows async height changes — markdown/highlight, images, collapses, viewport reflow), plus a floating scroll-to-bottom button. FIX: restore the transcript scrollbar — the refactor moved overflow-y-auto to an inner child, so the flex-1 container needed min-h-0 to constrain instead of growing to content. harness: vitest-setup polyfills Element.scrollTo + ResizeObserver (jsdom implements neither), fixing App component tests. docs: backend-handoff pruned (CR-3 resolved/removed); added cwd/LSP verification courier (backend confirmed all 6 asks ✅); removed the resolved cache-warming-timer courier. Verified: svelte-check 0 errors, biome clean, 523 tests pass, vite build OK.
Diffstat (limited to 'backend-handoff.md')
-rw-r--r--backend-handoff.md60
1 files changed, 28 insertions, 32 deletions
diff --git a/backend-handoff.md b/backend-handoff.md
index 69deebf..99c2964 100644
--- a/backend-handoff.md
+++ b/backend-handoff.md
@@ -5,39 +5,33 @@
> **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.
-_Last updated: 2026-06-11 — **Cache-rate fix + retention + CR-3 consumed FE-side** (from `frontend-cache-warming-handoff.md`): (1) per-turn cache rate now reads true on Claude (no FE change); (2) NEW cross-turn **expected cache (retention)** metric in the chat metrics bubble (`computeExpectedCachePct`/`viewExpectedCache`); (3) **CR-3 DONE & consumed** — the countdown is now AUTHORITATIVE off the surface's `cache-warming-timer` `nextWarmAt`/`lastWarmAt` (FE guessing dropped), history keyed off `lastWarmAt`, and `WarmResponse.expectedCacheRate` headlined on "Warm now"; (4) second "cache retention" `stat` parsed. transport mirror regenerated. **Earlier (same day):** `NumberField` + conversation-scoped subscriptions + "Cache Warming" sidebar view. Open asks: CR-1 (Loaded Extensions as a real multi-column table); CR-2 (optional catalog `scope` flag). **CR-3 is RESOLVED** (see §2)._
+_Last updated: 2026-06-11. **FE is current on `[email protected]`.** All handoffs to date are
+consumed: surfaces + WS, conversation transcript/metrics, tabs + model selector, cache-warming (incl.
+authoritative timer + retention + cache-rate fix), and **per-conversation cwd + LSP status** (new
+`workspace` feature — cwd field in the Model view + a "Language Servers" view; works for drafts too).
+**Open asks:** CR-1 (Loaded Extensions as a real table) + CR-2 (optional catalog `scope` flag) below.
+The cwd/LSP draft-path verification (`backend-handoff-cwd-lsp.md`) came back **all ✅ confirmed** by the
+backend (answers in their `frontend-lsp-cwd-handoff.md`) — see §2._
---
## 1. Pinned backend contracts (consumed by the FE)
-Pinned as `file:` deps: **`[email protected]`; `[email protected]`; `[email protected]`**.
+Pinned as `file:` deps: **`[email protected]`; `[email protected]`; `[email protected]`**.
| Package | Used for |
|---|---|
| `@dispatch/ui-contract` | surfaces + surface WS protocol |
| `@dispatch/wire` | `Chunk`/`StoredChunk`(+`seq`)/`ChatMessage`/`AgentEvent`/`TurnSealedEvent`/`Usage`/`StepId` + metrics: `StepMetrics`/`TurnMetrics`, `usage.stepId`, `step-complete`, `done.durationMs`/`done.usage`, `tool-result.durationMs` |
-| `@dispatch/transport-contract` | `ChatRequest`/`ModelsResponse`/`ConversationHistoryResponse`/`ConversationMetricsResponse` + WS chat ops + `WsClientMessage`/`WsServerMessage` |
+| `@dispatch/transport-contract` | `ChatRequest`/`ModelsResponse`/`ConversationHistoryResponse`/`ConversationMetricsResponse` + `WarmRequest`/`WarmResponse` + `CwdResponse`/`SetCwdRequest` + LSP (`LspStatusResponse`/`LspServerInfo`/`LspServerState`) + WS chat ops + `WsClientMessage`/`WsServerMessage` |
-Endpoints in use (HTTP **24203**, WS **24205**, CORS `*`):
+Endpoints in use (HTTP **24203**, WS **24205**, CORS `*` incl. `PUT`):
`POST /chat` (NDJSON) · `GET /models` · `GET /conversations/:id?sinceSeq=<n>` ·
-`GET /conversations/:id/metrics` · WS `chat.send`→`chat.delta`.
+`GET /conversations/:id/metrics` · `GET`/`PUT /conversations/:id/cwd` ·
+`GET /conversations/:id/lsp` · `POST /chat/warm` · WS `chat.send`→`chat.delta`.
Mirrored in-repo for headless agents: `.dispatch/{ui-contract,wire,transport-contract}.reference.md`
-(regenerate on any contract bump).
-
-**2026-06-11 re-mirror (cache-warming).** Both `ui-contract` and `transport-contract` were left at their
-existing versions by the backend (`[email protected]`, `[email protected]`) but gained ADDITIVE
-members; the `file:` deps already resolve them. The FE mirrors were regenerated to match:
-- `ui-contract.reference.md`: `NumberField` (`kind:"number"`) + optional `conversationId?` on
- `Subscribe`/`Unsubscribe`/`Invoke`/`Surface`/`SurfaceUpdate`.
-- `transport-contract.reference.md`: `POST /chat/warm` (`WarmRequest`/`WarmResponse`) + the throughput
- axis (`GET /metrics/throughput`, `ThroughputResponse`/`ThroughputModelStat`/`ThroughputPeriod`).
-- FE consumed: generic `number` renderer; protocol keyed by `surfaceId` carrying the focused
- conversationId with a staleness rule (drop a `surface`/`update` echoing a non-current conversation;
- a global no-echo reply is always accepted); store auto-subscribes every catalog surface with the
- focused conversationId and re-scopes on conversation switch; `warmNow()` posts `/chat/warm` with the
- conversation's current model name.
+(regenerate on any contract bump; all current as of `[email protected]`).
## 2. Open asks FOR THE BACKEND
@@ -90,24 +84,26 @@ trip per global surface per switch; no user-visible bug, the old spec is retaine
flicker). An optional `scope?: "global" | "conversation"` on `SurfaceCatalogEntry` would let the FE
skip re-subscribing globals on switch. **Not blocking** — only raise if cheap.
-### CR-3 — next-warm timestamp + manual-warm timer reset → **RESOLVED ✅ (backend `bfbad3a`, consumed FE-side)**
+### cwd + LSP draft path → **VERIFIED ✅ (all 6 asks confirmed; courier `backend-handoff-cwd-lsp.md`)**
-Both asks shipped by the backend (no contract bump — `custom` escape hatch) and are now consumed:
-1. **`nextWarmAt` / `lastWarmAt` (epoch-ms)** arrive on the conversation-scoped `cache-warming` surface
- as a `custom` field `{ rendererId: "cache-warming-timer", payload: { nextWarmAt, lastWarmAt } }`.
- FE: `parseControls` reads them; the countdown is now derived straight from `nextWarmAt`
- (`secondsUntilNext(nextWarmAt, now)`) and the history keys off `lastWarmAt` (`observeWarm`). The
- old FE best-effort anchor/guess logic was DELETED.
-2. **Manual `POST /chat/warm` now re-arms the timer + pushes a surface `update`.** FE: dropped the
- workaround of recording history from the HTTP response — history is driven authoritatively by the
- surface's `lastWarmAt`; the HTTP `WarmResponse` is still used for the immediate "Warm now" feedback
- line (now headlining `expectedCacheRate`). The generic surface-host does NOT render
- `cache-warming-timer` (no registered renderer → graceful skip); the cache-warming feature owns it.
+The backend confirmed all six asks (answers in their `frontend-lsp-cwd-handoff.md`, code refs
+`transport-http/src/app.ts` + `session-orchestrator/src/orchestrator.ts`; live-verified): unseen-id
+`GET /cwd`⇒`{cwd:null}` and `GET /lsp`⇒`{cwd:null,servers:[]}` (no 404/500); `PUT /cwd` on a draft id
+upserts by key; **draft cwd carries into turn 1** when `/chat` omits `cwd`; CORS preflight for `PUT` is
+answered; no LSP spawn while `cwd` is null; errors are `{error:string}`. **No backend change needed —
+the draft→first-message cwd path the FE built is fully supported.**
-(The standalone courier `backend-handoff-cache-warming-timer.md` is now historical — no open asks.)
+**FE invariant to KEEP (don't regress):** the chat send must **omit** `cwd` (send `undefined`), never
+`cwd:""`/`cwd:null`. The `/chat` `cwd` field treats any non-`undefined` value as "provided", so a literal
+empty would override the persisted draft cwd. Verified safe today: `chat/store.svelte.ts` builds
+`chat.send` with only `type`/`conversationId`/`message`/`model` — no `cwd` field. (The backend offered to
+harden `/chat` to treat blank as "not provided" if we ever want it — not needed while we omit the field.)
## 3. Likely NEXT backend asks (heads-up, not yet requested)
- `GET /conversations` — conversation list / sidebar (history explorer / switcher); could also expose a
per-conversation "last model" so a reopened tab seeds its model from the server instead of localStorage.
- `POST /conversations/:id/cancel` — "stop generating".
+- **LSP status over WS** (push) — today the FE HTTP-polls `GET /conversations/:id/lsp` on panel mount /
+ cwd change + a manual refresh; a live surface/WS push would remove the manual refresh and reflect a
+ server flipping to `error`/`connected` without a reload. (Backend flagged this as a future option.)