# Phase 12: Native Tablo Chat - Context

**Gathered:** 2026-05-16
**Status:** Ready for planning

<domain>
## Phase Boundary

Phase 12 delivers native per-tablo discussion in the Go+HTMX app. Each authenticated authorized user can see persisted message history, post text messages with server validation and CSRF protection, receive new messages in real time, and see unread indicators without any managed chat provider or external realtime runtime. The first implementation stays functional and plain, uses Xtablo-owned Postgres and Go infrastructure, and does not add a broader sharing/membership product beyond today's owner-only tablo model.

</domain>

<decisions>
## Implementation Decisions

### Discussion Surface
- **D-01:** Add discussion as a tab inside the existing tablo detail page.
- **D-02:** The tab label is `Discussion`.
- **D-03:** Tab order is `Overview / Tasks / Files / Events / Discussion`; discussion comes after Events.
- **D-04:** Direct loads such as `/tablos/{id}/discussion` must render the full tablo detail layout with Discussion active, matching the existing full-page tab fallback pattern.
- **D-05:** Dashboard tablo cards should show an unread badge, so Phase 12 needs basic unread/read-state support.

### Message Row Behavior
- **D-06:** Each message row shows author, absolute timestamp, and message text.
- **D-07:** Messages render oldest at the top and newest at the bottom.
- **D-08:** The composer lives at the bottom of the Discussion tab.
- **D-09:** Include simple day separators such as `May 16, 2026`.
- **D-10:** Empty state copy should be plain: `No messages yet` plus a short prompt like `Start the discussion for this tablo.`

### Message Lifecycle
- **D-11:** Do not expose edit UI in Phase 12.
- **D-12:** Do not expose delete UI in Phase 12.
- **D-13:** Keep nullable edit/delete metadata in the schema to satisfy CHAT-03 and preserve future compatibility.
- **D-14:** Exact future delete semantics are undecided. Do not force a tombstone/hard-delete UX decision into Phase 12 planning.
- **D-15:** The composer is a plain textarea plus send button with server-side validation.
- **D-16:** Avoid Enter-to-send behavior in the first version; it would add extra client-side behavior beyond the functional HTMX slice.

### Realtime Transport
- **D-17:** Lock the Phase 12 transport target to SSE receive + CSRF-protected HTMX POST send.
- **D-18:** SSE disconnects should silently reconnect through browser/EventSource behavior. Do not add visible reconnect UI unless normal send/history requests fail.
- **D-19:** Stream newly created messages and enough unread-badge updates to keep dashboard/tablo-card badges fresh.
- **D-20:** A send POST should render the sender's message immediately in the response. SSE broadcasts updates to other open tabs/views.
- **D-21:** Do not use WebSockets for the first Go chat version unless planning discovers a hard blocker for SSE.

### Participant Model
- **D-22:** Phase 12 stays owner-only under the current Go tablo ownership model. Do not introduce a minimal members table or invitation model in this phase.
- **D-23:** Message authors display name if available/added, with email fallback.
- **D-24:** The current Go `users` table only has email. Planner may add nullable display-name fields only if needed; otherwise email fallback is acceptable.
- **D-25:** Discussion header can show a simple participant count, e.g. `1 participant`.
- **D-26:** Unread badges use persistent per-user/tablo read state, even though the first version is owner-only. This supports durable reload behavior and future sharing.

### the agent's Discretion
- Exact migration names, sqlc query names, handler/template names, and HTMX fragment boundaries are planner discretion if they preserve the decisions above and match current Go backend patterns.
- Exact unread badge styling and empty-state wrapper layout are planner discretion, but must remain restrained and consistent with the functional Go+HTMX UI.
- Exact max message length is planner/research discretion, but CHAT-06 requires a server-side maximum and escaped rendering.

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Product Scope
- `.planning/ROADMAP.md` - Phase 12 goal, success criteria, user-in-loop transport decision, and SSE recommendation.
- `.planning/REQUIREMENTS.md` - CHAT-01 through CHAT-06, out-of-scope managed providers, and deferred chat ideas.
- `.planning/PROJECT.md` - v2 constraints: Go + HTMX, Postgres, no managed chat provider, plain functional UI.

### Prior Go Patterns
- `.planning/phases/10-events/10-CONTEXT.md` - Recent tablo tab routing, full-page fallback, and child-resource authorization decisions.
- `.planning/phases/11-individual-planning/11-CONTEXT.md` - Recent protected full-page route and plain agenda UI decisions.
- `backend/templates/tablos.templ` - Tablo detail tab bar, tab fragment integration point, and dashboard card pattern.
- `backend/internal/web/router.go` - Protected route group, tab route mounting order, and static-before-parametric route pattern.
- `backend/internal/web/handlers_tablos.go` - Tablo ownership helper and detail-page loading conventions.
- `backend/internal/web/handlers_events.go` - Recent child-resource form, validation, and full-tab refresh patterns.
- `backend/internal/web/handlers_events_test.go` - DB-backed authorization and full-router testing patterns for tablo child resources.
- `backend/templates/events.templ` - Existing tab template patterns and restrained functional UI style.
- `backend/templates/auth_form_errors.templ` - Field error rendering for server-validated forms.

### Historical Chat Reference
- `apps/chat-worker/src/index.ts` - Historical custom chat API and WebSocket worker reference only; Phase 12 chooses Go SSE + POST instead of this Cloudflare Durable Object runtime.
- `apps/chat-worker/src/durable-objects/ChatRoom.ts` - Historical message persistence/broadcast behavior reference only; do not copy the Durable Object architecture.
- `packages/tablo-views/src/TabloDiscussionSection.tsx` - Historical discussion placement and chat surface reference.
- `packages/tablo-views/src/hooks/useChat.ts` - Historical message history, send, and realtime behavior reference.
- `packages/tablo-views/src/hooks/useChatUnread.ts` - Historical unread-count behavior reference.
- `packages/chat-ui/src/security.ts` - Historical text/security helpers for bidi stripping and message truncation ideas.
- `packages/chat-ui/src/components/chat.tsx` - Historical rich chat UI reference only; Phase 12 intentionally avoids reactions, typing, read receipts, files, voice, and rich media.

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `TabloDetailPage` already supports active tabs and full-page fallback behavior; Discussion should follow the Events/Tasks/Files pattern.
- `TabloCard` is the dashboard integration point for unread badges.
- Existing `loadOwnedTablo`-style authorization should gate discussion history, posting, SSE streams, read state, and unread counts.
- Existing server-rendered form/error helpers support a textarea composer with CSRF and validation feedback.
- Existing test helpers (`setupTestDB`, full router requests, session cookies, CSRF extraction) support DB-backed chat tests.

### Established Patterns
- Tablo-scoped resources mount under `/tablos/{id}/...` inside `auth.RequireAuth`.
- Unauthorized access to another user's tablo-scoped resources returns 404 rather than 403.
- SQLC query files are canonical; generated Go files are reproduced with `just generate`.
- Mutations that affect a whole tab can return refreshed tab fragments for reliability.
- Primary pages and pushed tab URLs should work without relying on JavaScript-only state.

### Integration Points
- Add migrations for messages and per-user/tablo read state after current migrations.
- Add SQLC queries for message history, create message, unread counts, mark-read state, and SSE polling/listen support chosen during planning.
- Add `DiscussionDeps` or equivalent to router construction.
- Add Discussion tab route, full-page fallback, post route, and SSE stream endpoint under protected tablo routes.
- Extend dashboard/list query or add unread-count query so dashboard cards can render unread badges.
- Add templates for Discussion tab, message list rows, day separators, empty state, composer, and unread badge.

</code_context>

<specifics>
## Specific Ideas

- Discussion is a normal fifth tablo tab, not a separate top-level chat product.
- First UI should feel like a persisted work discussion, not a full consumer chat app.
- Dashboard unread badges are in scope, but typing indicators and presence are not.
- The sender should see the posted message from the POST response immediately; the realtime stream keeps other open views fresh.

</specifics>

<deferred>
## Deferred Ideas

- Typing indicators and presence are deferred to a future chat polish phase.
- Message edit UI is deferred.
- Message delete UI is deferred.
- Full membership/invitation model is deferred; Phase 12 remains owner-only.
- Rich chat features from the historical React chat UI are deferred: reactions, replies, read receipts, file/image messages, voice messages, pinned messages, search, threads, link previews, and rich media.
- Exact future delete semantics are deferred.

</deferred>

---

*Phase: 12-Native Tablo Chat*
*Context gathered: 2026-05-16*
