# Message Editing & Forking — Implementation **Version:** 0.7.x (Phase 2 per ARCHITECTURE.md §8) **Depends on:** parent_id (006), channel_cursors (008) — both deployed --- ## Status: Complete (backend + frontend) ### Backend (already existed) - [x] Migration 013 (`deleted_at`, `sibling_index`) - [x] `tree.go` — `getActivePath`, `getActiveLeaf`, `getPathToLeaf`, `getSiblings`, `findLeafFromMessage`, `nextSiblingIndex`, `updateCursor` - [x] `completion.go` — `loadConversation` walks active path via recursive CTE, `persistMessage` is cursor-aware with sibling_index, returns new message ID - [x] `messages.go` — `GetActivePath`, `EditMessage`, `Regenerate` (streaming + sibling), `UpdateCursor`, `ListSiblings` - [x] Routes wired in `main.go` ### Frontend (new work) - [x] `api.js` — `getActivePath`, `editMessage`, `streamRegenerate`, `updateCursor`, `listSiblings` - [x] `app.js` — tree-aware message flow: - `selectChat` uses `/path` endpoint - `sendMessage` reloads path after streaming for server-authoritative IDs - `regenerateMessage(id)` creates sibling via message-specific endpoint - `editMessage(id)` → inline edit → `submitEdit` → sibling + completion - `switchSibling(id, direction)` navigates branches via cursor update - `reloadActivePath()` refreshes from server after every mutation - Legacy `regenerate()` button delegates to `regenerateMessage(lastAssistantId)` - [x] `ui.js`: - `_messageHTML` renders ‹ 1/3 › branch indicators at fork points - Edit button on user messages, Regen button on assistant messages - `showEditInline` — textarea with Ctrl+Enter submit, Escape cancel - [x] `styles.css` — `.branch-nav`, `.branch-arrow`, `.branch-pos`, `.msg-edit-input`, `.msg-edit-actions`, `.msg-edit-cancel`, `.msg-edit-submit` --- ## API Endpoints | Method | Path | Purpose | |--------|------|---------| | `GET` | `/channels/:id/path` | Active path with sibling metadata | | `POST` | `/channels/:id/messages/:msgId/edit` | Create sibling user message | | `POST` | `/channels/:id/messages/:msgId/regenerate` | Create sibling assistant response (SSE) | | `PUT` | `/channels/:id/cursor` | Switch active branch | | `GET` | `/channels/:id/messages/:msgId/siblings` | List siblings at a fork point | --- ## Design Decisions 1. **`sibling_index` column** — explicit integer, not derived from `created_at`. Tree is self-describing. 2. **New `/path` endpoint** — `ListMessages` retained for admin/debug. Frontend uses `/path` exclusively. 3. **Two-step edit** — API: edit creates sibling, completion is separate. Frontend chains them. 4. **Uniform ‹ › arrows** — same UI regardless of how fork was created (edit, regen, explicit). 5. **Message-specific regenerate** — replaces old channel-level stub. Bottom-bar button delegates. 6. **Branch switch → leaf** — clicking mid-tree sibling walks to deepest descendant via `findLeafFromMessage`. 7. **Path reload after every mutation** — `reloadActivePath()` called post-send, edit, regen, abort. --- ## User Flows ### Edit a message 1. Hover user message → click **Edit** 2. Textarea replaces message text (Ctrl+Enter to submit, Escape to cancel) 3. Submit → `POST /messages/:id/edit` (creates sibling) → `POST /chat/completions` (new response) 4. Branch indicator appears: ‹ 1/2 › 5. Old branch preserved, navigable via arrows ### Regenerate a response 1. Hover assistant message → click **Regen** (or bottom-bar ↻ button) 2. `POST /messages/:id/regenerate` streams new response as sibling 3. Branch indicator appears: ‹ 1/2 › 4. Original response preserved, navigable via arrows ### Navigate branches 1. Click ‹ or › on any fork point 2. `GET /messages/:id/siblings` → pick adjacent → `PUT /cursor` with sibling ID 3. Backend walks to leaf, returns new path 4. Entire conversation below fork point updates to show selected branch --- ## Testing Checklist - [ ] Existing linear conversations load and render correctly (backward compat) - [ ] New messages in linear conversation work as before - [ ] Regen creates sibling, shows ‹ 1/2 ›, original preserved - [ ] Multiple regens show ‹ 1/3 ›, all navigable - [ ] Edit creates sibling user message + triggers new completion - [ ] Branch arrows navigate correctly (← older, → newer) - [ ] Context sent to LLM is active path only (no cross-branch pollution) - [ ] Cursor persists across page reload (reopening chat shows last-viewed branch) - [ ] Mobile: edit textarea usable, branch arrows tappable