docs(12-03): complete discussion realtime plan

This commit is contained in:
Arthur Belleville 2026-05-16 10:34:41 +02:00
parent 409245eac0
commit 81538bb780
No known key found for this signature in database
4 changed files with 159 additions and 15 deletions

View file

@ -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

View file

@ -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 |
---

View file

@ -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

View file

@ -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*