xtablo-source/.planning/phases/12-native-tablo-chat/12-03-SUMMARY.md
2026-05-16 10:34:41 +02:00

6.8 KiB

phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
12-native-tablo-chat 03 discussion
chat
discussion
sse
realtime
htmx
templ
phase plan provides
12-native-tablo-chat 01 discussion message persistence and POST send
phase plan provides
12-native-tablo-chat 02 persistent read state and unread badge behavior
Authenticated owner-only SSE stream endpoint
In-process discussion event broker
Local EventSource browser integration
Duplicate suppression between sender POST response and SSE event
Composer reset after successful sends
discussion
router
static-assets
templates
tests
added patterns
Realtime receive uses SSE from the Go process; sends remain CSRF-protected HTMX POSTs.
Discussion SSE events carry rendered message HTML with message IDs for duplicate suppression.
Composer reset is attached directly to the HTMX form after successful requests.
created modified
backend/internal/web/discussion_broker.go
backend/static/discussion-sse.js
backend/internal/web/handlers_discussion.go
backend/internal/web/handlers_discussion_test.go
backend/internal/web/router.go
backend/cmd/web/main.go
backend/templates/discussion.templ
backend/templates/discussion_forms.go
backend/templates/layout.templ
Use in-process pub/sub for the first native realtime slice; Postgres remains the reconnect/missed-event source of truth.
Do not add WebSockets, managed realtime providers, workers, or runtime CDN scripts.
Keep sender POST rendering, but suppress duplicate rows when SSE wins the race.
Use `hx-on::after-request` on the composer form for clearing input, avoiding fragile delegated HTMX event source lookup.
Discussion routes accept `DiscussionDeps{Queries, Realtime}`; router provides a default broker when none is injected.
SSE handlers set `text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`, and `X-Accel-Buffering: no`.
Local JS silently reconnects through EventSource and exposes no connection-status, typing, or presence UI.
CHAT-04
CHAT-05
~45min 2026-05-16

Phase 12 Plan 03: Discussion Realtime Summary

Native realtime discussion delivery is implemented with Go-owned SSE receive and HTMX POST send.

Performance

  • Duration: ~45 min
  • Started: 2026-05-16T08:16:00Z
  • Completed: 2026-05-16T08:33:05Z
  • Tasks: 4
  • Files modified: 9

Accomplishments

  • Added RED tests for stream authentication, owner-only access, SSE headers, broker publishing, broker cancellation cleanup, and browser script behavior.
  • Added DiscussionBroker with per-tablo subscriptions, non-blocking publish, and unregister-on-cancel behavior.
  • Added protected GET /tablos/{id}/discussion/stream with SSE headers, initial flush, keepalives, and request-context cancellation.
  • Wired discussion POST to broadcast newly inserted messages after DB persistence and read-state update.
  • Added local /static/discussion-sse.js EventSource integration with duplicate suppression.
  • Added form-level HTMX reset behavior so successful sends clear the composer reliably.
  • Completed browser UAT: one visible row per sent message, composer clears, second tab receives messages without manual refresh, and no external realtime provider is used.

Task Commits

  1. Task 1: Add RED SSE handler and broker tests - c6dcb68
  2. Task 2: Implement SSE broker, stream route, and local browser glue - d15c374
  3. Realtime UAT fixes: duplicate suppression and composer reset - 6f17c30, 426d89c, 1034efc, 409245e

Files Created/Modified

  • backend/internal/web/discussion_broker.go - Adds in-process discussion pub/sub.
  • backend/internal/web/handlers_discussion.go - Adds stream handler and publish-after-insert behavior.
  • backend/internal/web/handlers_discussion_test.go - Adds SSE, broker, and browser-script regression coverage.
  • backend/internal/web/router.go - Adds discussion stream route and default broker wiring.
  • backend/cmd/web/main.go - Wires production discussion broker.
  • backend/templates/discussion.templ - Adds stream URL hook and form-level reset behavior.
  • backend/templates/discussion_forms.go - Adds stream URL helper.
  • backend/templates/layout.templ - Loads local discussion SSE script.
  • backend/static/discussion-sse.js - Adds EventSource append and duplicate suppression.

Decisions Made

  • The broker broadcasts persisted messages only after the database insert succeeds.
  • The browser script does not surface connection state; EventSource handles silent reconnect.
  • Sender duplicate prevention stays client-side because the same user may legitimately have a second tab that should receive the SSE event.
  • Composer reset is attached to the HTMX form instead of a delegated script handler after HTMX v2 event detail differences made delegated source lookup brittle.

Deviations from Plan

  • UAT found two UI regressions in the initial SSE implementation: sender-visible duplicate rows and composer text not clearing. Both were fixed before close-out.

Issues Encountered

  • HTMX v2 event details did not expose the source form where the initial delegated reset handler expected it.
  • Canceling duplicate swaps meant detail.successful was not reliable for reset behavior, so reset moved onto the form with hx-on::after-request.

User Setup Required

None.

Verification

  • RED command failed before implementation because SSE types/routes did not exist: TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./internal/web -run 'TestDiscussionStream|TestDiscussionBroker|TestDiscussionPostBroadcasts' -count=1
  • cd backend && just generate passed.
  • cd backend && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./internal/web -run 'TestDiscussionStream|TestDiscussionBroker|TestDiscussionPostBroadcasts|TestDiscussion' -count=1 passed.
  • cd backend && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1 passed.
  • git diff --check passed.
  • Browser UAT approved by user on 2026-05-16 after duplicate-row and composer-reset fixes.

Self-Check: PASSED

  • CHAT-04 is covered by SSE implementation and browser UAT.
  • CHAT-05 is satisfied with only Xtablo-owned Go/Postgres/static infrastructure.
  • No WebSocket, managed chat, external realtime runtime, or runtime CDN script was added.
  • Stream auth, ownership, headers, broker publish, and cancellation cleanup have automated coverage.

Phase 12 Readiness

Phase 12 is ready for final phase verification and close-out.


Phase: 12-native-tablo-chat Completed: 2026-05-16