4.5 KiB
4.5 KiB
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)
- Migration 013 (
deleted_at,sibling_index) tree.go—getActivePath,getActiveLeaf,getPathToLeaf,getSiblings,findLeafFromMessage,nextSiblingIndex,updateCursorcompletion.go—loadConversationwalks active path via recursive CTE,persistMessageis cursor-aware with sibling_index, returns new message IDmessages.go—GetActivePath,EditMessage,Regenerate(streaming + sibling),UpdateCursor,ListSiblings- Routes wired in
main.go
Frontend (new work)
api.js—getActivePath,editMessage,streamRegenerate,updateCursor,listSiblingsapp.js— tree-aware message flow:selectChatuses/pathendpointsendMessagereloads path after streaming for server-authoritative IDsregenerateMessage(id)creates sibling via message-specific endpointeditMessage(id)→ inline edit →submitEdit→ sibling + completionswitchSibling(id, direction)navigates branches via cursor updatereloadActivePath()refreshes from server after every mutation- Legacy
regenerate()button delegates toregenerateMessage(lastAssistantId)
ui.js:_messageHTMLrenders ‹ 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
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
sibling_indexcolumn — explicit integer, not derived fromcreated_at. Tree is self-describing.- New
/pathendpoint —ListMessagesretained for admin/debug. Frontend uses/pathexclusively. - Two-step edit — API: edit creates sibling, completion is separate. Frontend chains them.
- Uniform ‹ › arrows — same UI regardless of how fork was created (edit, regen, explicit).
- Message-specific regenerate — replaces old channel-level stub. Bottom-bar button delegates.
- Branch switch → leaf — clicking mid-tree sibling walks to deepest descendant via
findLeafFromMessage. - Path reload after every mutation —
reloadActivePath()called post-send, edit, regen, abort.
User Flows
Edit a message
- Hover user message → click Edit
- Textarea replaces message text (Ctrl+Enter to submit, Escape to cancel)
- Submit →
POST /messages/:id/edit(creates sibling) →POST /chat/completions(new response) - Branch indicator appears: ‹ 1/2 ›
- Old branch preserved, navigable via arrows
Regenerate a response
- Hover assistant message → click Regen (or bottom-bar ↻ button)
POST /messages/:id/regeneratestreams new response as sibling- Branch indicator appears: ‹ 1/2 ›
- Original response preserved, navigable via arrows
Navigate branches
- Click ‹ or › on any fork point
GET /messages/:id/siblings→ pick adjacent →PUT /cursorwith sibling ID- Backend walks to leaf, returns new path
- 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