97 lines
4.5 KiB
Markdown
97 lines
4.5 KiB
Markdown
# 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
|