From 81538bb7801dd0fa87c946c55b4b194770c3b625 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sat, 16 May 2026 10:34:41 +0200 Subject: [PATCH] docs(12-03): complete discussion realtime plan --- .planning/REQUIREMENTS.md | 6 +- .planning/ROADMAP.md | 2 +- .planning/STATE.md | 24 +-- .../12-native-tablo-chat/12-03-SUMMARY.md | 142 ++++++++++++++++++ 4 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 .planning/phases/12-native-tablo-chat/12-03-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index d67a2cc..10a129c 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -21,8 +21,8 @@ Requirements for milestone v2.0. Each requirement must map to exactly one roadma - [x] **CHAT-01**: Each tablo has a discussion view where authenticated authorized users can see persisted message history - [x] **CHAT-02**: User can post a text message to a tablo discussion with validation and CSRF protection - [x] **CHAT-03**: Messages are stored in Postgres with tablo, author, body, created timestamp, and deletion/edit metadata -- [ ] **CHAT-04**: Open tablo discussion views receive new messages in real time without a manual refresh -- [ ] **CHAT-05**: Real-time delivery uses Xtablo-owned infrastructure only; no managed chat or realtime provider is introduced +- [x] **CHAT-04**: Open tablo discussion views receive new messages in real time without a manual refresh +- [x] **CHAT-05**: Real-time delivery uses Xtablo-owned infrastructure only; no managed chat or realtime provider is introduced - [x] **CHAT-06**: Message rendering escapes user content and enforces a server-side maximum body length ### Etapes @@ -99,7 +99,7 @@ Populated during roadmap creation. | ETAPE-01..06 | Phase 9 | Pending | | EVENT-01..05 | Phase 10 | Complete | | PLAN-01..04 | Phase 11 | Pending | -| CHAT-01..06 | Phase 12 | Pending | +| CHAT-01..06 | Phase 12 | Complete | **Coverage:** - v2.0 requirements: 27 total diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 3dc74f1..d450a16 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -16,7 +16,7 @@ | 9 | Etapes | Tasks can be grouped under one-level etapes without breaking the kanban model | ETAPE-01..06 | | 10 | 4/4 | Complete | 2026-05-16 | | 11 | 2/2 | Complete | 2026-05-16 | -| 12 | 2/3 | In Progress| | +| 12 | 3/3 | Complete | 2026-05-16 | --- diff --git a/.planning/STATE.md b/.planning/STATE.md index c354e38..575db98 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v2.0 milestone_name: Collaboration, planning, and social sign-in status: executing -last_updated: "2026-05-16T08:16:14.486Z" -last_activity: 2026-05-16 -- Phase 12 planning complete +last_updated: "2026-05-16T08:33:45.416Z" +last_activity: 2026-05-16 -- Phase 12 execution complete progress: total_phases: 5 - completed_phases: 4 + completed_phases: 5 total_plans: 18 - completed_plans: 17 - percent: 94 + completed_plans: 18 + percent: 100 --- # STATE @@ -24,15 +24,15 @@ progress: See: `.planning/PROJECT.md` (updated 2026-05-15) **Core value:** A user can sign in and run the Tablos workflow — organize work, attach files, discuss, and plan scheduled events — without a JS framework or managed chat provider. -**Current focus:** Phase 12 — Native Tablo Chat +**Current focus:** v2.0 milestone verification ## Current Position Phase: 12 -Plan: Not started -Status: Ready to execute -Last activity: 2026-05-16 -- Phase 12 planning complete -Resume file: .planning/phases/12-native-tablo-chat/12-01-PLAN.md +Plan: 03 complete +Status: Complete +Last activity: 2026-05-16 -- Phase 12 execution complete +Resume file: .planning/phases/12-native-tablo-chat/12-03-SUMMARY.md ## Phase Status @@ -42,13 +42,14 @@ Resume file: .planning/phases/12-native-tablo-chat/12-01-PLAN.md | 9 | Etapes | ◆ UAT passed; security pending | | 10 | Events | ✓ Complete | | 11 | Individual Planning | ✓ Complete | -| 12 | Native Tablo Chat | ◆ Ready to execute | +| 12 | Native Tablo Chat | ✓ Complete | ## Verification Record - 2026-05-15: Phase 8 execution complete. Local verification passed with `go test ./... -count=1`; database-backed integration coverage skips unless `TEST_DATABASE_URL` is configured. - 2026-05-15: Phase 9 UAT complete. Five browser checkpoints passed after fixing the selected-etape create gap; backend verification passed with `TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1`. - 2026-05-16: Phase 10 execution complete. Events UAT approved; backend verification passed with `TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1`. +- 2026-05-16: Phase 12 execution complete. Discussion realtime UAT approved after duplicate-row and composer-reset fixes; backend verification passed with `just generate` and `TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1`. ## Decisions @@ -123,6 +124,7 @@ Resume file: .planning/phases/12-native-tablo-chat/12-01-PLAN.md | Phase 11 P02 | ~20min | 2 tasks | 4 files | | Phase 12 P01 | ~25min | 3 tasks | 22 files | | Phase 12 P02 | ~25min | 2 tasks | 5 files | +| Phase 12 P03 | ~45min | 4 tasks | 9 files | ## Notes diff --git a/.planning/phases/12-native-tablo-chat/12-03-SUMMARY.md b/.planning/phases/12-native-tablo-chat/12-03-SUMMARY.md new file mode 100644 index 0000000..d474a35 --- /dev/null +++ b/.planning/phases/12-native-tablo-chat/12-03-SUMMARY.md @@ -0,0 +1,142 @@ +--- +phase: 12-native-tablo-chat +plan: 03 +subsystem: discussion +tags: [chat, discussion, sse, realtime, htmx, templ] + +requires: + - phase: 12-native-tablo-chat + plan: 01 + provides: discussion message persistence and POST send + - phase: 12-native-tablo-chat + plan: 02 + provides: persistent read state and unread badge behavior +provides: + - 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 +affects: [discussion, router, static-assets, templates, tests] + +tech-stack: + 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." + +key-files: + created: + - backend/internal/web/discussion_broker.go + - backend/static/discussion-sse.js + modified: + - 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 + +key-decisions: + - "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." + +patterns-established: + - "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." + +requirements-completed: [CHAT-04, CHAT-05] + +duration: ~45min +completed: 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*