summaryrefslogtreecommitdiffhomepage
path: root/frontend-workspaces-handoff.md
diff options
context:
space:
mode:
Diffstat (limited to 'frontend-workspaces-handoff.md')
-rw-r--r--frontend-workspaces-handoff.md216
1 files changed, 0 insertions, 216 deletions
diff --git a/frontend-workspaces-handoff.md b/frontend-workspaces-handoff.md
deleted file mode 100644
index 1fed1bf..0000000
--- a/frontend-workspaces-handoff.md
+++ /dev/null
@@ -1,216 +0,0 @@
-# Backend handoff — Workspaces (backend → FE) — courier doc
-
-> **From:** arch-rewrite orchestrator · **To:** frontend orchestrator · **Courier:** the user.
-> Response to `backend-handoff-workspaces.md`. This doc finalizes the contract shapes
-> the backend will implement. The FE should re-pin `@dispatch/wire` and
-> `@dispatch/transport-contract` `file:` deps and re-mirror any `.dispatch/*.reference.md`.
-
-## Version bumps
-
-| Package | From | To | Notes |
-|---|---|---|---|
-| `@dispatch/wire` | `0.11.0` | `0.12.0` | Additive: `Workspace`, `WorkspaceEntry`, `ConversationMeta.workspaceId` |
-| `@dispatch/transport-contract` | `0.15.0` | `0.16.0` | Additive: workspace endpoints + `workspaceId` on chat/queue ops |
-| `@dispatch/ui-contract` | `0.2.0` | `0.2.0` | **Unchanged** |
-
----
-
-## 1. Final types — `@dispatch/[email protected]`
-
-```ts
-/**
- * A named, URL-driven grouping of conversations that owns a default cwd.
- * Every conversation belongs to exactly one workspace; conversations that
- * haven't set their own per-conversation cwd inherit `defaultCwd`.
- */
-export interface Workspace {
- /** The URL slug (immutable). Lowercase `[a-z0-9-]`, 1–40 chars. */
- readonly id: string;
- /** Display title (editable). Defaults to `id` on creation. */
- readonly title: string;
- /** The workspace's default cwd, or `null` (fall through to server default). */
- readonly defaultCwd: string | null;
- /** Epoch-ms when the workspace was first created. */
- readonly createdAt: number;
- /** Epoch-ms of the most recent conversation activity in this workspace. */
- readonly lastActivityAt: number;
-}
-
-/**
- * A workspace entry in the list response — a `Workspace` plus a conversation count.
- */
-export interface WorkspaceEntry extends Workspace {
- /** Number of conversations assigned to this workspace. */
- readonly conversationCount: number;
-}
-```
-
-`ConversationMeta` gains a required `workspaceId`:
-
-```ts
-export interface ConversationMeta {
- readonly id: string;
- readonly createdAt: number;
- readonly lastActivityAt: number;
- readonly title: string;
- readonly status: ConversationStatus;
- /** Always present; "default" for legacy/unspecified conversations. */
- readonly workspaceId: string;
- readonly compactedFrom?: string;
-}
-```
-
----
-
-## 2. Final types — `@dispatch/[email protected]`
-
-### Additive fields on existing request types
-
-```ts
-export interface ChatRequest {
- readonly conversationId?: string;
- readonly message: string;
- readonly model?: string;
- readonly cwd?: string;
- readonly reasoningEffort?: ReasoningEffort;
- /** Workspace to assign the conversation to. Default "default". Auto-creates if missing. */
- readonly workspaceId?: string;
-}
-
-export interface QueueRequest {
- readonly text: string;
- /** Default "default". Auto-creates if missing. */
- readonly workspaceId?: string;
-}
-
-export interface ChatQueueMessage {
- readonly type: "chat.queue";
- readonly conversationId: string;
- readonly text: string;
- /** Default "default". Auto-creates if missing. */
- readonly workspaceId?: string;
-}
-```
-
-### Workspace endpoint types
-
-```ts
-/** Body of `PUT /workspaces/:id` (all fields optional — the ensure/create call). */
-export interface EnsureWorkspaceRequest {
- /** Display title. Default: the workspace id. Only used on create; ignored if workspace exists. */
- readonly title?: string;
- /** Default cwd. Default: null (inherit server default). Only used on create. */
- readonly defaultCwd?: string | null;
-}
-
-/** Response of GET/PUT /workspaces/:id — the workspace itself. */
-export interface WorkspaceResponse extends Workspace {}
-
-/** Response of `GET /workspaces` — all workspaces sorted by lastActivityAt desc. */
-export interface WorkspaceListResponse {
- readonly workspaces: readonly WorkspaceEntry[];
-}
-
-/** Body of `PUT /workspaces/:id/title`. */
-export interface SetWorkspaceTitleRequest {
- readonly title: string;
-}
-
-/** Body of `PUT /workspaces/:id/default-cwd`. null/absent = clear to server default. */
-export interface SetWorkspaceDefaultCwdRequest {
- readonly defaultCwd: string | null;
-}
-
-/** Response of `DELETE /workspaces/:id`. */
-export interface DeleteWorkspaceResponse {
- readonly workspaceId: string;
- /** Conversations that were closed (status → "closed") by this delete. */
- readonly closedCount: number;
-}
-```
-
----
-
-## 3. Final endpoint list
-
-| Method & Path | Body | Returns | Notes |
-|---|---|---|---|
-| `GET /workspaces` | — | `WorkspaceListResponse` | Sorted by `lastActivityAt` desc. Includes `conversationCount`. |
-| `PUT /workspaces/:id` | `EnsureWorkspaceRequest?` | `WorkspaceResponse` | **Create-on-miss** (idempotent). Creates with `title=id`, `defaultCwd=null` if missing. Returns existing as-is if present. Slug validated. |
-| `GET /workspaces/:id` | — | `WorkspaceResponse` | Pure read. 404 if missing. |
-| `PUT /workspaces/:id/title` | `SetWorkspaceTitleRequest` | `WorkspaceResponse` | Rename (display only; id unchanged). |
-| `PUT /workspaces/:id/default-cwd` | `SetWorkspaceDefaultCwdRequest` | `WorkspaceResponse` | Set/clear workspace default cwd. |
-| `DELETE /workspaces/:id` | — | `DeleteWorkspaceResponse` | **Closes all conversations** (status → "closed"), reassigns them to "default", then deletes the workspace. 409 for `"default"`. |
-| `GET /conversations` | `?workspaceId=`, `?status=`, `?q=` | `ConversationListResponse` | Additive `?workspaceId=` filter, composable with existing filters. |
-| `DELETE /conversations/:id/cwd` | — | `CwdResponse` | Clears explicit conversation cwd (returns `cwd: null`). |
-
-### Existing endpoints (semantic note, no type change)
-
-- `GET /conversations/:id/cwd` — unchanged: returns the **explicit** conversation cwd (`null` = inheriting workspace default).
-- `GET /conversations/:id/lsp` — now roots LSP at the **effective** cwd; `LspStatusResponse.cwd` returns the effective cwd.
-
----
-
-## 4. cwd resolution (backend-owned)
-
-```
-effectiveCwd = conversationStore.getCwd(conversationId) // explicit per-conversation
-if (effectiveCwd == null) {
- workspaceId = conversationStore.getWorkspaceId(conversationId) // "default" fallback
- workspace = conversationStore.getWorkspace(workspaceId)
- effectiveCwd = workspace?.defaultCwd ?? null
-}
-if (effectiveCwd == null) effectiveCwd = serverDefaultCwd // process.cwd() today
-```
-
-- `GET /conversations/:id/cwd` → explicit cwd only (`null` = inherit).
-- `GET /conversations/:id/lsp` → effective cwd.
-- Turn start (`runTurn` / `warm`) → effective cwd.
-
----
-
-## 5. `DELETE /workspaces/:id` semantics
-
-1. Close all conversations in that workspace (set `status = "closed"`).
-2. Reassign their `workspaceId` to `"default"` (so no dangling reference).
-3. Delete the workspace entity.
-4. Return `{ workspaceId, closedCount }`.
-5. `DELETE /workspaces/default` → HTTP 409.
-
-Closed conversations are hidden from tab-restore (`?status=active,idle` excludes `closed`).
-
----
-
-## 6. Workspace lifecycle / auto-creation
-
-- **Auto-create on turn start:** if `workspaceId` is provided and doesn't exist, the backend auto-creates it (`title = id`, `defaultCwd = null`).
-- **`PUT /workspaces/:id` create-on-miss:** if absent, creates with optional `title`/`defaultCwd` from the body (defaults: `title = id`, `defaultCwd = null`). If present, returns existing as-is.
-- **Slug validation:** `^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$` (1–40 chars, lowercase, digits, internal hyphens only). Reject invalid with 400. No normalization. `"default"` allowed but non-deletable.
-- **`"default"` workspace:** always synthesized if not persisted; guaranteed in `GET /workspaces` list.
-- **`lastActivityAt`:** updates when a conversation in the workspace appends, or on first creation. Does NOT update on title/default-cwd changes.
-- **Compaction:** post-compaction conversations inherit the original's `workspaceId`.
-
----
-
-## 7. Answers to FE open questions (Q1–Q8)
-
-| # | Decision |
-|---|---|
-| Q1 | **Close all conversations** in the workspace (status → "closed"), reassign to "default", then delete the workspace. Return `closedCount`. |
-| Q2 | **Add `DELETE /conversations/:id/cwd`** to clear explicit cwd (fall back to workspace default). `PUT` validation unchanged (empty string still 400). |
-| Q3 | **Deferred to v1** — no WS lifecycle push. Fetch-on-mount + manual refresh sufficient. Can add `workspace.created/updated/deleted` later, additively. |
-| Q4 | **`PUT /workspaces/:id`** is the create-on-miss entry point (idempotent, 200). `GET /workspaces/:id` is a pure read (404 if missing). |
-| Q5 | Slug regex `^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$`. Reject, don't normalize. `"default"` non-deletable. |
-| Q6 | `Workspace` in `@dispatch/wire`. Request/response bodies in `@dispatch/transport-contract`. |
-| Q7 | Confirmed — backend does nothing beyond `workspaceId` on `ConversationMeta` + `?workspaceId=` filter. |
-| Q8 | Yes — post-compaction conversations inherit `workspaceId`. `forkHistory` copies it. |
-
----
-
-## 8. Gaps resolved (from FE handoff §3)
-
-1. **Unknown workspaceId on turn start** → auto-create (title = id, defaultCwd = null). Typos can be deleted.
-2. **PUT /workspaces/:id initial state** → body accepts optional `title`/`defaultCwd` with defaults (`title = id`, `defaultCwd = null`). Only applied on create; existing workspace returned as-is.
-3. **lastActivityAt on title/default-cwd changes** → no.
-4. **LSP cwd field** → returns effective cwd.
-5. **Conversation count in list** → yes, included as `WorkspaceEntry.conversationCount`.