feat(wiki): live wiki-editing presence banner (LC-498) #495

Merged
longjacksonle merged 3 commits from feat/LC-498-wiki-editing-presence into main 2026-06-29 20:54:00 +02:00

LC-498: Live wiki editing presence (the viable slice)

The ticket flags TRUE collaborative co-editing (CRDT/OT, per-keystroke shared state) as [DEFER] - it fights the server-render + OOB-fragment model. The ticket's own recommendation is the presence-only compromise, and that's what this ships: a live "X is editing the wiki" banner on the room About page, reusing the typing-indicator infrastructure. True co-edit stays deferred.

What a user sees

While someone has the room wiki editor open, everyone else viewing that room's About page sees a live " is editing the wiki..." banner under the Wiki heading. It appears as soon as they focus the editor and clears a few seconds after they blur, save, or navigate away. Last-writer-wins on save is unchanged - this is awareness, not locking.

How it works (mirrors the typing indicator)

  • Hub (ws/hub.rs): a wiki_editing DashMap parallel to typing, with notify_wiki_editing / stop_wiki_editing. A heartbeat broadcasts WikiEditing to the room except the editor's own connection, then a 5s idle-eviction task broadcasts WikiStoppedEditing. Ephemeral, no DB.
  • Events: new ChatEvent::WikiEditing / WikiStoppedEditing (transient, like UserTyping).
  • WS frame: ClientFrame::WikiEditing { room_id }, handled exactly like typing (gated on room subscription).
  • Render: render_event arms + ws/wiki_editing.html / ws/wiki_stopped_editing.html, OOB-swapping #wiki-editing. Recipient-independent (the typing fragment is too, so like typing the banner text is not localized).
  • Client: the About page subscribes to the room (data-lc-live-room) and has a #wiki-editing slot outside #wiki-content (survives the edit/view fragment swaps). The wiki editor fragment heartbeats {type:'wiki_editing'} on focus + every 3s + on input via the existing window.__lcWS; the 3s heartbeat keeps the banner up through reading/thinking pauses, and the 5s server eviction clears it after the editor leaves.

Scope

Presence only. No new dependency, no DB change, no schema migration; reuses the WebSocket hub, the typing pattern, and the existing wiki edit/view fragments. No saas gating needed (room-scoped, not admin-only). True concurrent co-editing remains deferred per the ticket.

Tests

just check (clippy standalone + saas + desktop + fmt), just test, just test-saas all pass. New hub unit test wiki_editing_presence_excludes_editor_and_clears (banner goes to viewers not the editor, no duplicate mid-session broadcast, idempotent stop).

Manual smoke test (recommend before merge)

Two browsers, same room: open the wiki editor in one, confirm the other's About page shows "X is editing the wiki..." and that it clears a few seconds after save / blur.

## LC-498: Live wiki editing presence (the viable slice) The ticket flags TRUE collaborative co-editing (CRDT/OT, per-keystroke shared state) as `[DEFER]` - it fights the server-render + OOB-fragment model. The ticket's own recommendation is the presence-only compromise, and that's what this ships: a live **"X is editing the wiki"** banner on the room About page, reusing the typing-indicator infrastructure. True co-edit stays deferred. ### What a user sees While someone has the room wiki editor open, everyone else viewing that room's About page sees a live "<name> is editing the wiki..." banner under the Wiki heading. It appears as soon as they focus the editor and clears a few seconds after they blur, save, or navigate away. Last-writer-wins on save is unchanged - this is awareness, not locking. ### How it works (mirrors the typing indicator) - **Hub** (`ws/hub.rs`): a `wiki_editing` DashMap parallel to `typing`, with `notify_wiki_editing` / `stop_wiki_editing`. A heartbeat broadcasts `WikiEditing` to the room **except the editor's own connection**, then a 5s idle-eviction task broadcasts `WikiStoppedEditing`. Ephemeral, no DB. - **Events**: new `ChatEvent::WikiEditing` / `WikiStoppedEditing` (transient, like `UserTyping`). - **WS frame**: `ClientFrame::WikiEditing { room_id }`, handled exactly like `typing` (gated on room subscription). - **Render**: `render_event` arms + `ws/wiki_editing.html` / `ws/wiki_stopped_editing.html`, OOB-swapping `#wiki-editing`. Recipient-independent (the typing fragment is too, so like typing the banner text is not localized). - **Client**: the About page subscribes to the room (`data-lc-live-room`) and has a `#wiki-editing` slot outside `#wiki-content` (survives the edit/view fragment swaps). The wiki editor fragment heartbeats `{type:'wiki_editing'}` on focus + every 3s + on input via the existing `window.__lcWS`; the 3s heartbeat keeps the banner up through reading/thinking pauses, and the 5s server eviction clears it after the editor leaves. ### Scope Presence only. No new dependency, no DB change, no schema migration; reuses the WebSocket hub, the typing pattern, and the existing wiki edit/view fragments. No saas gating needed (room-scoped, not admin-only). True concurrent co-editing remains deferred per the ticket. ### Tests `just check` (clippy standalone + saas + desktop + fmt), `just test`, `just test-saas` all pass. New hub unit test `wiki_editing_presence_excludes_editor_and_clears` (banner goes to viewers not the editor, no duplicate mid-session broadcast, idempotent stop). ### Manual smoke test (recommend before merge) Two browsers, same room: open the wiki editor in one, confirm the other's About page shows "X is editing the wiki..." and that it clears a few seconds after save / blur.
The presence-only slice of live wiki co-editing (true CRDT/OT co-edit stays deferred - it fights the OOB-fragment model). Adds a wiki_editing DashMap mirroring the typing map, plus notify_wiki_editing / stop_wiki_editing: a heartbeat broadcasts WikiEditing to the room except the editor and evicts after 5s of silence; stop broadcasts WikiStoppedEditing to all. New ChatEvent variants WikiEditing / WikiStoppedEditing. Unit-tested (excludes the editor, no re-broadcast mid-session, idempotent stop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the ClientFrame::WikiEditing heartbeat handler (gated on room subscription, like typing) and the WikiEditing / WikiStoppedEditing render arms in render_event, backed by ws/wiki_editing.html + ws/wiki_stopped_editing.html (OOB-swap #wiki-editing). Recipient-independent like the typing fragment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(wiki): live "X is editing the wiki" banner on the About page (LC-498)
All checks were successful
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 4s
check-secrets / Kingfisher (push) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 13s
check-secrets / Kingfisher (pull_request) Successful in 15s
Check / clippy + fmt + tests (pull_request) Successful in 3m13s
Create release / Create release from merged PR (pull_request) Has been skipped
fdf001fe3f
The room About page now subscribes to the room (data-lc-live-room) and carries a #wiki-editing banner slot outside #wiki-content (so it survives the edit/view fragment swaps). The wiki editor fragment heartbeats a wiki_editing frame on focus + every 3s + on input while focused, so other viewers see who is editing; the banner clears ~5s after the editor blurs, saves, or navigates away.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-06-29 20:53:47 +02:00
longjacksonle deleted branch feat/LC-498-wiki-editing-presence 2026-06-29 20:54:01 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!495
No description provided.