Migrate active-peer to /events?peer= query param #16
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
posta/chat#16
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What to build
Implement ADR 0004. Replace the tab-ID handshake + POST mechanism with a query parameter on the
/eventsSSE connection.Server (
internal/web/events.go):handleEventsreads?peer=<url>from the request, validates it, stores it onSubscriber.activePeervia the hub.mintTabID,subscriberRegistry,handleActivePeer, thePOST /events/active-peerroute registration, the synthesized downstreamreadySSE frame, and thepush-diag.events.subscribe/push-diag.active_peer.postlog lines tied to this path.Client (
internal/web/static/live.js):/events?peer=<url>where<url>comes frompeerFromPath(location.pathname).tab-readylistener, the active-peer POST, thetabIDstate.Tests (
internal/web/events_test.go):?peer=in place of the POST.Acceptance criteria
POST /events/active-peerroute returns 404 (handler deleted)GET /events?peer=https://example.comregisters the subscriber with the correct active peeranyActive=truestill worksBlocked by
None - can start immediately
Agent Brief
Category: enhancement
Summary: Replace the tab-ID + POST-active-peer mechanism with a
peerquery parameter on the/eventsSSE connection. Net deletion of ~150 LOC across server and client.Current behavior:
Each browser tab opens a long-lived
/eventsSSE stream. On stream open, the server mints a per-tab token (tabID) and emits a synthesizedreadyevent carrying it. The browser stashes thetabIDin JS memory, then issuesPOST /events/active-peerwith{peer, tab_id}on every navigation (initial, htmx:afterSwap, popstate, page resume). The server keys a subscriber registry on(sessionID, tabID)to disambiguate two tabs sharing one session cookie, and uses it to route the SetActivePeer call to the rightSubscriberatomic.Desired behavior:
The browser opens
/events?peer=<url>where<url>is derived fromlocation.pathname(empty string when not on a thread). When the path changes — htmx swap, popstate, page resume — the bridge closes the currentEventSourceand opens a new one with the new peer. The server reads the query parameter on each subscribe, stores it onSubscriber.activePeer, and discards everything related to per-tab identity. Two tabs sharing a session each have their own/eventsconnection and therefore their ownSubscriberandactivePeer— naturally distinguished, no tokens needed.The bet captured in ADR 0004: posta-server's
Last-Event-IDreplay covers the ~50ms event-loss window during an active-peer change. If we observe lost events in practice, the fix is upstream, not by reintroducing tab-IDs.Key behavioral details:
peer=(empty). Server treats missing and empty identically: subscriber has empty active peer.Subscriber.activePeer, so the change is transparent once the field is populated correctly.tab-ready/readysynthesized SSE event is deleted from the wire. The/eventsHTTP response itself is the browser's signal that the stream is live.What gets deleted:
Server side:
POST /events/active-peerroute registration and its handler.(sessionID, tabID)-keyed map and theRegister/Deregister/SetActivePeermethods on it).readySSE frame in the events handler.push-diag.events.subscribeandpush-diag.active_peer.postlog lines tied to this path (the broader push-diag cleanup is a separate issue, but these specific lines die naturally with the code).Client side (in
internal/web/static/live.js):tab-ready(currentlyready) listener on theEventSource.fetch()to/events/active-peer.tabIDmodule-level state.readysyncing logic — replaced by attaching the peer at connection-open time.Tests:
(sessionID, tabID)registry tests inevents_test.go.GET /events?peer=https://example.comresults in a subscriber whoseActivePeer()returns the right URL./eventsconnections with different?peer=values each maintain their own active peer correctly.Acceptance criteria:
POST /events/active-peerreturns 404 (handler and route deleted)GET /events?peer=https://example.comregisters a subscriber whose active peer ishttps://example.comGET /events?peer=registers a subscriber whose active peer is the empty string/c/fooand one on/c/bar, each have their own correct active peer (verifiable via auto-advance behavior)anyActive=truestill passOut of scope: