# Phase 12: Native Tablo Chat - Patterns

**Mapped:** 2026-05-16

## Closest Existing Analogs

| New Concern | Closest Existing File | Reuse Pattern |
|-------------|-----------------------|---------------|
| Discussion routes and deps | `backend/internal/web/router.go` | Add explicit deps to `NewRouter`; mount protected static segments before parametric children. |
| Owned tablo child resource | `backend/internal/web/handlers_events.go` | Call `loadOwnedTablo`, return 404 for invalid/non-owned, render HTMX fragment or full detail page. |
| DB-backed handler tests | `backend/internal/web/handlers_events_test.go` | Use `setupTestDB`, `sessionCookieForUser`, `getCSRFToken`, full router requests, and response/DB assertions. |
| Tab content integration | `backend/templates/tablos.templ` | Add fifth tab link and active-tab branch inside `TabloDetailPage`. |
| Child tab template | `backend/templates/events.templ` | Keep a single tab fragment with form/list subcomponents and restrained slate/blue styling. |
| Form errors | `backend/templates/auth_form_errors.templ` | Reuse `FieldError` and `GeneralError` for textarea validation. |
| Nullable text params | `backend/internal/web/handlers_events.go` | Use `pgtype.Text{String: s, Valid: s != ""}`. |
| Local static assets | `backend/templates/layout.templ`, `backend/embed.go` | Serve script from `/static/...`; embedding includes all static files. |
| Dashboard card extension | `backend/templates/tablos.templ` | Keep badge small inside the existing card header; no layout rewrite. |

## File Mapping

| File | Role | Notes |
|------|------|-------|
| `backend/migrations/0009_discussion.sql` | Schema | Add `discussion_messages` and `discussion_read_states`; include constraints/indexes. |
| `backend/internal/db/queries/discussion.sql` | sqlc queries | History, insert, read-state upsert, unread counts, message lookup after insert. |
| `backend/internal/web/handlers_discussion.go` | HTTP handlers | Discussion tab, send, stream, read-state helpers. |
| `backend/internal/web/discussion_broker.go` | SSE broker | Small per-process pub/sub with context cleanup and keepalive support. |
| `backend/internal/web/handlers_discussion_test.go` | Tests | DB-backed contracts for all discussion behavior. |
| `backend/templates/discussion.templ` | UI | Message list, rows, day separators, composer, empty state, SSE container targets. |
| `backend/templates/discussion_forms.go` | View models | Form/errors, message row formatting, date grouping, unread badge helpers. |
| `backend/templates/tablos.templ` | Integration | Add Discussion tab and dashboard unread badge support. |
| `backend/static/discussion-sse.js` | Browser EventSource glue | Local, minimal, no framework; append fragments and apply OOB badge updates. |

## Required Local Conventions

- Keep sqlc-generated Go files uncommitted; execution runs `cd backend && just generate`.
- Keep all discussion routes inside `auth.RequireAuth`.
- Use `loadOwnedTablo` before any discussion query or stream registration.
- Keep non-owner behavior as 404, not 403.
- Use templ escaped expressions for message body and metadata.
- Keep all added JS local and small; no CDN, no client framework.
- Preserve non-HTMX fallback for history page and send redirect/refresh behavior.

## Decision Coverage Map

| Decisions | Implementation Surface |
|-----------|------------------------|
| D-01/D-02/D-03/D-04 | `TabloDetailPage` fifth tab, `/tablos/{id}/discussion` handler. |
| D-05/D-26 | `discussion_read_states`, dashboard unread queries, badge template. |
| D-06/D-07/D-08/D-09/D-10 | `discussion.templ` message list, day grouping, composer, empty state. |
| D-11/D-12/D-13/D-14 | Schema nullable edit/delete metadata; no UI controls. |
| D-15/D-16 | Composer textarea/button only. |
| D-17/D-18/D-19/D-20/D-21 | HTMX POST send, SSE stream, local script, no WebSockets. |
| D-22/D-23/D-24/D-25 | Owner-only handlers, email fallback author label, `1 participant`. |
