feat(realtime): declarative live-update subscription module (LC-156 phase 1) #192

Merged
nrupard merged 2 commits from feat/lc-156-live-subscribe-foundation into main 2026-05-22 21:52:52 +02:00
Owner

What

Phase 1 of LC-156 (live updates by default): the foundational shared subscribe mechanism. Design: docs/superpowers/specs/2026-05-22-live-updates-by-default-design.md.

The room, DM, and voice pages each hand-rolled an identical htmx:wsOpen subscribe + htmx:beforeCleanupElement teardown IIFE (100% duplication). This replaces all three with one shared assets/live.js loaded once from base.html.

How

  • A page opts into WS room updates by putting data-lc-live-room="<id>" on any element.
  • live.js sends {type:"subscribe",room_id} on socket open (scanning the whole document, using the wrapper from the event detail so it does not depend on layout.html's own wsOpen listener ordering) and on htmx:load for any live element in a freshly-loaded subtree (using window.__lcWS).
  • Loaded once with document-level listeners, so it cannot stack duplicate listeners across reconnect soft-refreshes - the reason the IIFEs needed teardown. Re-subscribing an already-subscribed room is a no-op server-side (HashSet); no unsubscribe is sent (matches prior behavior).

No server or protocol change: keeps the existing room_id subscribe frame. This is a behavior-preserving deduplication that establishes the mechanism.

Deferred to follow-up sub-issues (filed)

  • Typed topics (enclave:/user:/admin:) on the hub + subscribe-time authorization.
  • Per-surface stale-page fills (enclave member/room lists, invitations, admin lists, settings, saved, inbox, activity).

Test

just check (Askama templates compile), just test, just test-saas green - the route tests that render the room/DM/voice pages confirm the templates still render with the new attribute. (just verify was blocked locally by a port already in use, unrelated to the change; the release build itself succeeded.) JS runtime behavior is not unit-tested in this repo (the replaced IIFEs were not either); the change is a faithful consolidation of identical logic.

Part of the LC-159 post-audit story.

🤖 Generated with Claude Code

## What Phase 1 of LC-156 (live updates by default): the foundational shared subscribe mechanism. Design: `docs/superpowers/specs/2026-05-22-live-updates-by-default-design.md`. The room, DM, and voice pages each hand-rolled an identical `htmx:wsOpen` subscribe + `htmx:beforeCleanupElement` teardown IIFE (100% duplication). This replaces all three with one shared `assets/live.js` loaded once from base.html. ## How - A page opts into WS room updates by putting `data-lc-live-room="<id>"` on any element. - `live.js` sends `{type:"subscribe",room_id}` on socket open (scanning the whole document, using the wrapper from the event detail so it does not depend on layout.html's own `wsOpen` listener ordering) and on `htmx:load` for any live element in a freshly-loaded subtree (using `window.__lcWS`). - Loaded once with document-level listeners, so it cannot stack duplicate listeners across reconnect soft-refreshes - the reason the IIFEs needed teardown. Re-subscribing an already-subscribed room is a no-op server-side (HashSet); no unsubscribe is sent (matches prior behavior). No server or protocol change: keeps the existing `room_id` subscribe frame. This is a behavior-preserving deduplication that establishes the mechanism. ## Deferred to follow-up sub-issues (filed) - Typed topics (`enclave:`/`user:`/`admin:`) on the hub + subscribe-time authorization. - Per-surface stale-page fills (enclave member/room lists, invitations, admin lists, settings, saved, inbox, activity). ## Test `just check` (Askama templates compile), `just test`, `just test-saas` green - the route tests that render the room/DM/voice pages confirm the templates still render with the new attribute. (`just verify` was blocked locally by a port already in use, unrelated to the change; the release build itself succeeded.) JS runtime behavior is not unit-tested in this repo (the replaced IIFEs were not either); the change is a faithful consolidation of identical logic. Part of the LC-159 post-audit story. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(realtime): declarative live-update subscription module (LC-156 phase 1)
All checks were successful
check-secrets / Nosey parker (push) Successful in 6s
check-secrets / Kingfisher (push) Successful in 9s
check-secrets / TruffleHog (push) Successful in 9s
check-secrets / Nosey parker (pull_request) Successful in 6s
check-secrets / TruffleHog (pull_request) Successful in 8s
check-secrets / Kingfisher (pull_request) Successful in 11s
Check / clippy + fmt + tests (pull_request) Successful in 3m30s
ec3615c3e7
Foundation for "live updates by default" (LC-156). The room, DM, and voice pages each hand-rolled an identical htmx:wsOpen subscribe + htmx:beforeCleanupElement teardown IIFE (100% duplication). Replace all three with one shared assets/live.js loaded once from base.html: a page opts into WS room updates by putting data-lc-live-room="<id>" on any element, and the module sends the {type:"subscribe",room_id} frame on socket open and when such an element loads into the DOM.

Because the module is loaded once and registers document-level listeners, it cannot accumulate duplicate listeners across reconnect soft-refreshes (the reason the per-page IIFEs needed explicit teardown), so the teardown dance is gone. On wsOpen it subscribes every live element in the document using the wrapper from the event detail (so it does not depend on layout.html's own wsOpen listener having set window.__lcWS first); on htmx:load it subscribes any live elements in the freshly-loaded subtree using window.__lcWS. Re-subscribing an already-subscribed room is harmless (the server's subscriber set is a HashSet); no unsubscribe is sent, matching prior behavior.

No server or protocol change: this keeps the existing room_id subscribe frame. Typed topics (enclave/user/admin + subscribe-time authorization) and the per-surface stale-page fills are deliberately deferred to follow-up sub-issues; see docs/superpowers/specs/2026-05-22-live-updates-by-default-design.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(realtime): also re-subscribe on htmx:afterSettle, not just htmx:load (LC-156)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 3m28s
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 5s
check-secrets / TruffleHog (push) Successful in 6s
check-secrets / Nosey parker (push) Successful in 6s
check-secrets / Kingfisher (pull_request) Successful in 8s
check-secrets / Nosey parker (pull_request) Successful in 6s
Create release / Create release from merged PR (pull_request) Has been skipped
7b2605d5a8
Review follow-up: the swap-into-an-open-socket case (notably the reconnect soft-refresh that replaces #main without dropping the socket) is driven through htmx:afterSettle in this app - the same event the sidebar mention-count and voice re-scan logic key on - whereas live.js only listened on htmx:load. Listen on both so the live root is re-subscribed regardless of which event a given swap emits. Re-subscribing is idempotent server-side (HashSet), so the overlap is free. (Plain room navigation is a full <a href> load, which re-handshakes and fires htmx:wsOpen, so that path was already covered.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nrupard deleted branch feat/lc-156-live-subscribe-foundation 2026-05-22 21:52:53 +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!192
No description provided.