Skip to content

feat: UP-21 message edit/versioning#39

Merged
Killea merged 6 commits intoKillea:mainfrom
bertheto:feat/msg-edit-versioning
Mar 4, 2026
Merged

feat: UP-21 message edit/versioning#39
Killea merged 6 commits intoKillea:mainfrom
bertheto:feat/msg-edit-versioning

Conversation

@bertheto
Copy link
Contributor

@bertheto bertheto commented Mar 4, 2026

Summary

  • Add inline message editing UI with save/cancel workflow (AcbMsgEditStart, AcbMsgEditCancel, AcbMsgEditHistory)
  • Backend: PUT /api/messages/{id} endpoint + GET /api/messages/{id}/history + full version history in message_edits table
  • System messages (role=system) are protected from editing (backend guard in crud.msg_edit + UI guard hides edit button)
  • SSE broadcast of msg.edit event for real-time in-place bubble update
  • MCP tool msg_edit exposed in mcp_server.py and tools/dispatch.py

Changes

Backend

  • src/db/crud.py: msg_edit(), msg_edit_history() with PermissionError guard for system messages
  • src/db/database.py: message_edits table schema
  • src/db/models.py: MessageEdit, MessageEditNoChangeError models
  • src/main.py: PUT /api/messages/{id} + GET /api/messages/{id}/history endpoints
  • src/mcp_server.py + src/tools/dispatch.py: MCP tool exposure

Frontend

  • src/static/index.html: inline edit UI, save/cancel handlers, SSE msg.edit handler, AcbMsgEditHistory modal
  • src/static/css/main.css: styles for .msg-edit-btn, .msg-edit-textarea, .msg-edit-actions, .msg-edited-indicator, .msg-edit-history-modal

Tests

  • tests/test_msg_edit.py: integration tests (edit, no-change, not-found, permission, system-role guard)
  • frontend/src/tests/message-edit.test.js: frontend unit tests (edit button, save flow, cancel, system guard)

Test plan

  • pytest tests/test_msg_edit.py - all tests pass
  • cd frontend && npx vitest run - all tests pass
  • Visual: edit a message, save -> new content persists, edited indicator appears
  • Visual: navigate away from thread and back -> edited content persists (loaded from DB)
  • System messages have no edit button in UI

bertheto added 4 commits March 4, 2026 14:55
Add full message edit capability with append-only version history.

DB: new message_edits table, edited_at + edit_version columns on messages,
FTS5 update trigger (DELETE+INSERT) to keep search index in sync.

CRUD: msg_edit validates author/system-only permission, no-op guard, content
filter, FTS5 sync, SSE msg.edit event. msg_edit_history returns all versions
ordered by version ASC. _row_to_message updated for new fields.

MCP: msg_edit + msg_edit_history tools. edited_by deduced via
get_connection_agent. handle_msg_get now includes edited_at/edit_version.

REST: PUT /api/messages/{id} (trust-the-caller, no auth until SEC-JWT-01) +
GET /api/messages/{id}/history. GET messages response now includes edited_at
and edit_version on every message.

Web UI: edit button (pencil, hover-reveal) on every bubble, inline editing
(textarea + Save/Cancel), edited indicator (dotted underline, clickable),
history modal listing all previous versions, SSE msg.edit handler for
real-time in-place bubble update.

Tests: 16 unit (in-memory), 6 integration (REST), 16 Vitest (jsdom). All pass.
…auth

Backend: crud.msg_edit now raises PermissionError when msg.role == 'system'.
System events/prompts can never be edited regardless of edited_by.

UI: appendBubble conditionally renders edit button only when !isSystem.

Tests: add test_msg_edit_system_role_cannot_be_edited (pytest) +
'system message row has no edit button' (vitest). Fix integration test
_create_thread_and_message: add X-Agent-Token header (POST /api/threads
and POST /api/messages) and use msg.author display name as edited_by.
AcbMsgEditCancel was called after a successful save, which restored the
original bubble content from dataset.originalContent, overwriting the
freshly rendered new text. Fix: instead of calling cancel, inline the
cleanup (remove msg-editing class, re-enable edit button, clear dataset)
so the new content rendered by AcbMessageRenderer is preserved.

Test: add assertion that bubble.textContent === 'new text' after save.
@bertheto bertheto force-pushed the feat/msg-edit-versioning branch from e45e9b6 to cf69034 Compare March 4, 2026 13:56
bertheto added 2 commits March 4, 2026 15:20
…eption tests

- Add _runtime_diag_payload() to src/main.py (missing after rebase from origin/main)
- Import DB_PATH from config (required by _runtime_diag_payload)
- Update /api/threads/{id}/agents endpoint to return structured 404/503 with diagnostics
  (was lost during conflict resolution, fixes test_thread_agents_not_found_includes_runtime_diagnostics)
- Replace pytest.raises() with explicit try/except in async unit tests for custom exceptions
  (pytest.raises() has inconsistent behavior with pytest-asyncio 1.3.0 on Linux for custom exceptions)
… clauses

Avoids class identity mismatch caused by Python's editable install (pip install -e .)
loading src.db.crud as two distinct module objects on Linux when the package is
installed. Using except crud.MessageEditNoChangeError / crud.MessageNotFoundError
guarantees both the raise and the except reference the exact same class object.
Also removes the now-unused direct imports of MessageEditNoChangeError / MessageNotFoundError.
@Killea Killea merged commit 9de7156 into Killea:main Mar 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants