ADR-0050: Live Frontend — SSE State Push, CodeMirror 6, Draft Presence¶
- Status: accepted
- Refined by: ADR-0051 (CM6 hosts the [[ doc-link autocompletion)
- Date: 2026-07-06
- Deciders: Nicolas Burri ("use the recommended options for P-1 to P-3")
- Refines: ADR-0032 (frontend realization — editor + live channel), ADR-0034 (domain events gain a post-commit client relay); relates to the DECIDED checkpoint live-frontend-design.md (P-1a/P-2a/P-3b)
Decisions¶
1. State push via Server-Sent Events (P-1a)¶
GET /api/events (SSE, session-authenticated). On connect the server resolves the caller's
authorized scope set and registers the connection; in-process domain events already published by
the lifecycle get a post-commit relay (never before commit — rolled-back transitions must not
be announced) broadcasting {scopeId, documentId, versionId, kind} (kinds v1: STATE_CHANGED,
DOCUMENT_MOVED, DOCUMENT_CREATED) to connections whose scope set contains the event's scope.
Payloads carry IDs only — push is a hint, never a data channel: the client refetches through
the normal RLS-checked API, so a relay bug cannot become a separation bug. Frontend: an
EventSource service invalidates the affected views (document list, open detail, inbox) with a
subtle "updated" affordance; no toasts in v1. Reconnect = native EventSource behavior; missed
events cost staleness until the refetch. Scope-set changes take effect on reconnect (bounded
latency, same accepted class as ADR-0028 re-evaluation).
2. Editor: CodeMirror 6 (P-2a)¶
The draft editor's textarea is replaced by CodeMirror 6 with markdown language support — highlighting, proper selection/transaction API for the existing toolbar, keymaps. Lazy-loaded (own chunk, mermaid pattern). The preview pipeline (MarkdownService) is unchanged. Deliberate strategic fit: CM6 is the host editor y-codemirror.next binds to — this is the groundwork for the future CRDT arc.
3. Draft presence + live refresh (P-3b)¶
Riding the §1 channel: entering/leaving a draft editor announces presence
(POST/DELETE /api/documents/versions/{id}/presence, held server-side with a short TTL +
heartbeat); the SSE relay broadcasts PRESENCE_CHANGED for the version's scope. Editor + detail
page show "N.N. is editing" chips; a draft-content save broadcasts the normal STATE-family event
so co-viewers refresh on save (not per keystroke). The optimistic-lock conflict path
(REQ-DOC-016) stays authoritative and becomes rarer in practice. Full CRDT co-editing (Yjs)
is explicitly deferred to its own post-demo ADR + requirements round — it supersedes
REQ-DOC-016 for collab drafts and changes audit attribution; not to be built en passant.
Consequences¶
- New SSE connection registry (in-process, per-instance — consistent with the modular monolith; a multi-instance deployment would need a relay bus, noted for the ops docs, out of scope now).
- Presence rows/TTL state are ephemeral (in-memory) — deliberately NOT records; no audit, no persistence, no RLS table (scope filtering happens at the relay, and presence payloads carry only user display name + version id the receiver is already authorized to see).
- Requirements: push/presence/editor rows → v1.6 pending (REQ-UI/REQ-NOT additions).
- Bundle: CM6 lazy chunk (~documented at build time); SSE costs one held connection per session (proxy timeout note → deployment.md).