From f45f7c6010be7be32edf6a4b2378989d166ea6ae Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sun, 17 May 2026 06:37:06 +0200 Subject: [PATCH] docs(17): research phase domain --- .../phases/17-chat-planning/17-RESEARCH.md | 576 ++++++++++++++++++ 1 file changed, 576 insertions(+) create mode 100644 .planning/phases/17-chat-planning/17-RESEARCH.md diff --git a/.planning/phases/17-chat-planning/17-RESEARCH.md b/.planning/phases/17-chat-planning/17-RESEARCH.md new file mode 100644 index 0000000..032f8d4 --- /dev/null +++ b/.planning/phases/17-chat-planning/17-RESEARCH.md @@ -0,0 +1,576 @@ +# Phase 17: Chat & Planning - Research + +**Researched:** 2026-05-17 +**Domain:** Go templ templates + CSS design system — visual restyle of placeholder views +**Confidence:** HIGH + +## Summary + +Phase 17 adds real content to two pages that are currently empty placeholder stubs and adds the CSS classes needed to style them. The `/chat` and `/planning` routes each return `AppSectionMainContent(...)` — a generic shell with a title and descriptive paragraph. Phase 17 replaces these stubs with actual views: a discussion message list with own-vs-others bubble alignment, and a planning event list with date-grouped separators. + +The CONTEXT.md references files like `backend/templates/discussion.templ` and `backend/internal/web/handlers_discussion.go`. These paths do NOT exist. The active backend is in `go-backend/`. All work happens in `go-backend/internal/web/views/dashboard_components.templ` (and a new companion `.go` file for view model helpers) plus `go-backend/internal/web/ui/app.css`. No discussion or planning data model exists in this codebase today — the spec requires building static/hardcoded view models first, matching the in-memory pattern used by tasks and the dashboard. + +The design system is complete and proven. Tokens in `base.css`, component classes in `card.css`, `empty-state.css`, and `app.css` are all ready to use. The `ui.EmptyState` and `.ui-card` components are already wired. The `overview-section` / `overview-section-heading` pattern exists in `dashboard_components.templ` (used by `OverviewProjectsSection`). The eight new CSS classes (`.message-row`, `.message-own`, `.message-other`, `.message-bubble`, `.message-meta` + variants) are the only net-new CSS additions required. + +**Primary recommendation:** Build `ChatMainContent()` and `PlanningMainContent()` as full templ components in `dashboard_components.templ`, add view-model types and helpers in a new `discussion_view.go` and `planning_view.go` file in the `views` package, and append the message-bubble CSS block to the end of `app.css`. Keep handlers unchanged — they already call `views.ChatMainContent()` and `views.PlanningMainContent()` with no parameters. + + +## User Constraints (from CONTEXT.md) + +### Locked Decisions + +**Chat Bubble Visual Design** +- D-M01: Own messages — max-width ~70%, right-aligned (`margin-left: auto`), class `.message-own`. Others — full-width, left-aligned, class `.message-other`. Each row is a flex container. +- D-M02: Own bubble background: `rgba(128, 78, 236, 0.10)` tint. Others: `var(--color-surface-default)`. Border: `var(--color-border-subtle)`. +- D-M03: Author email + timestamp appear ABOVE the bubble, mirrored to match bubble alignment. +- D-M04: All new CSS in `app.css` using `var(--...)` tokens only — no Tailwind inline, no hardcoded hex. + +**Discussion Data Model** +- D-D01: `DiscussionMessageView` gains `IsOwn bool`. +- D-D02: `loadDiscussionTabData` receives current user ID, passes it to `DiscussionMessagesFromRows`. +- D-D03: HTMX POST response path sets `IsOwn = true`; SSE broadcast sets `IsOwn = false`. +- D-D04: `DiscussionMessageFromRow` returns `IsOwn: false` by default; handler sets `.IsOwn = true` for the HTMX path. + +**Discussion Container** +- D-C01: `#discussion-messages` container uses `.ui-card` CSS class (replacing inline Tailwind border/bg classes). + +**Planning Page** +- D-P01: Planning heading wraps in `
` + `
` with `

Planning

`. +- D-P02: Add `PlanningShowDaySeparator(events []PlanningEventRow, index int) bool` helper. +- D-P03: Day separator element: `
`. +- D-P04: `DateLabel` removed from event row body — rendered in separator only. + +### Claude's Discretion +- Exact `max-width` value for own message bubbles (recommend 70%) +- Border-radius on bubbles (recommend `0.75rem 0.75rem 0.25rem 0.75rem` for own; `0.25rem 0.75rem 0.75rem 0.75rem` for others) +- Exact opacity/alpha for brand-primary tint (recommend 10-15%) +- Divider between messages: keep or drop `divide-y divide-slate-100` (recommend drop — bubbles carry own visual weight) +- Empty state for planning: adopt `@ui.EmptyState(...)` (consistent with Phase 16) +- Navigation controls in planning heading: keep `.ui-button` inline utility classes + +### Deferred Ideas (OUT OF SCOPE) +- Hover-to-reveal timestamp (compact mode) +- Message edit or delete +- Sidebar collapse toggle (JS) +- Mobile-responsive kanban + + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|------------------| +| CHAT-UI-01 | Discussion view uses consistent card/surface design with message bubbles distinguishing own vs. others | New `ChatMainContent()` templ replaces placeholder stub; `IsOwn bool` field drives CSS class branching; `.ui-card` wraps the list | +| PLAN-UI-01 | Planning page uses the overview-section layout for event aggregation | `PlanningMainContent()` templ replaces placeholder stub; `.overview-section` heading pattern from `OverviewProjectsSection` is the exact model to follow | + + +## Architectural Responsibility Map + +| Capability | Primary Tier | Secondary Tier | Rationale | +|------------|-------------|----------------|-----------| +| Chat message rendering | Frontend Server (SSR) | — | Go templ renders all HTML server-side; no client JS rendering | +| Own-vs-others alignment | Frontend Server (SSR) | — | `IsOwn bool` set in Go before template render; CSS classes do visual work | +| SSE new-message append | API / Backend | Browser / Client | Handler pushes `text/event-stream`; HTMX `hx-swap="beforeend"` appends fragment | +| Planning event grouping | Frontend Server (SSR) | — | `PlanningShowDaySeparator` helper runs server-side; no JS date logic | +| CSS design tokens | CDN / Static | — | Served as `/static/styles.css`; no build step needed for new app.css additions | + +## Standard Stack + +### Core (already in place — no new installs) +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| `a-h/templ` | current (go.mod) | Server-side HTML templating | Project-wide choice — all views use templ [VERIFIED: go-backend/go.mod pattern] | +| `go-chi/chi/v5` | current | Router | Already handles `/chat` and `/planning` routes [VERIFIED: router.go] | +| `htmx.org` | 4.0.0-beta2 | HTMX client | Already loaded in page `` via CDN [VERIFIED: dashboard_components.templ] | + +### Supporting CSS +| File | Purpose | Status | +|------|---------|--------| +| `go-backend/internal/web/ui/base.css` | Design tokens | Exists — 223 lines of `:root` vars [VERIFIED: read] | +| `go-backend/internal/web/ui/card.css` | `.ui-card` and section classes | Exists — 28 lines [VERIFIED: read] | +| `go-backend/internal/web/ui/empty-state.css` | `.ui-empty-state` classes | Exists [VERIFIED: ls] | +| `go-backend/internal/web/ui/app.css` | Global utility classes, overview-section, project grid | Exists — 1896 lines [VERIFIED: read] | + +**Installation:** None required. All dependencies are already present. + +## Architecture Patterns + +### System Architecture Diagram + +``` +Browser GET /chat or /planning + | + v +router.go → handlers/auth.go:renderAppPage() + | + v (authenticated) +views.ChatMainContent() or views.PlanningMainContent() + | + v +templ component renders HTML with view model + | + v +DashboardPage() or DashboardContentSwap() wraps content in app shell + | + v +Response: full page HTML or HTMX swap fragment +``` + +For chat post (SSE path): +``` +Browser POST /chat/messages (future — not in Phase 17 scope) +SSE handler sets message.IsOwn = false → renders DiscussionMessageRow → SSE stream +Same handler sets message.IsOwn = true → renders DiscussionMessageRow → HTMX response +``` + +Note: Phase 17 does NOT implement the SSE path or POST handler. The CONTEXT.md's D-D02/D-D03/D-D04 decisions concern a `handlers_discussion.go` that does not exist in this codebase. The SPEC requires building the view struct and template only — the handler currently calls `views.ChatMainContent()` with no parameters and no user context. + +### Recommended Project Structure + +New files for Phase 17: + +``` +go-backend/internal/web/views/ +├── dashboard_components.templ # MODIFY: replace PlanningMainContent() + ChatMainContent() stubs +├── discussion_view.go # NEW: DiscussionMessageView, DiscussionTabData, hardcoded demo data +└── planning_view.go # NEW: PlanningEventRow, PlanningTabData, PlanningShowDaySeparator, demo data + +go-backend/internal/web/ui/ +└── app.css # MODIFY: append ~60 lines of .message-* CSS classes +``` + +### Pattern 1: Replacing a Placeholder Stub + +Current pattern — identical for both pages: +```go +// Source: go-backend/internal/web/views/dashboard_components.templ (VERIFIED) +templ PlanningMainContent() { + @AppSectionMainContent("Planning", "Visualisez le rythme...") +} + +templ ChatMainContent() { + @AppSectionMainContent("Discussions", "Retrouvez les conversations...") +} +``` + +Replacement pattern — follow the tasks page model: +```go +// Source: go-backend/internal/web/views/dashboard_components.templ (VERIFIED: TasksMainContent example) +templ PlanningMainContent(data PlanningTabData) { + // real content using overview-section, day separators, event rows +} + +templ ChatMainContent(data DiscussionTabData) { + // real content using .ui-card, message bubbles +} +``` + +The handler must be updated to construct the view model and pass it in: +```go +// Source: go-backend/internal/web/handlers/auth.go (VERIFIED) +func (h *AuthHandler) GetPlanningPage() http.HandlerFunc { + return h.renderAppPage("/planning", func(user PublicUser) templ.Component { + return views.PlanningMainContent(views.NewPlanningTabData()) + }) +} +``` + +### Pattern 2: overview-section Heading + +Canonical example from `OverviewProjectsSection` (already in production): +```go +// Source: go-backend/internal/web/views/dashboard_components.templ (VERIFIED) +
+
+

Mes Projets

+
+ ... +
+``` + +Planning variant — use `
` + `
` with `

Planning

` and the date-range label + nav buttons on the right side. + +The `.overview-section-heading` CSS rule targets `h3` explicitly: +```css +/* Source: go-backend/internal/web/ui/app.css line 886 (VERIFIED) */ +.overview-section-heading h3, +.tasks-section-header h3 { + color: var(--color-surface-muted-inverse); + font-size: 1.6rem; + font-weight: 600; + margin: 0; +} +``` + +**Action required:** The heading rule only styles `h3` elements. Planning uses `

`. Either add `h1` to the `.overview-section-heading` selector rule in `app.css`, or apply typography directly in the template. Recommended: add `h1` to the selector to be consistent with existing rule structure. + +### Pattern 3: ui.EmptyState + +Already used in `tablos.templ` and the dashboard: +```go +// Source: go-backend/internal/web/views/tablos.templ (VERIFIED) +@ui.EmptyState(ui.EmptyStateProps{ + Title: "Aucun projet trouvé", + Description: "Créez votre premier projet", + Icon: ui.UIIcon("grid3x3"), + Action: ui.Button(ui.ButtonProps{...}), +}) +``` + +For planning empty state: `Title: "No events in this range"`, `Description: "Use the navigation controls to browse another 14-day window."`, no Action button needed. + +### Pattern 4: In-Memory Demo Data + +Established pattern for pages that don't yet have a real data source (from `overviewTasks()` and `overviewQuickActions()` in `home.go`): +```go +// Source: go-backend/internal/web/views/home.go (VERIFIED) +func overviewTasks() []dashboardTask { + return []dashboardTask{ + {Title: "yo", Project: "Hello", ...}, + ... + } +} +``` + +Both `DiscussionTabData` and `PlanningTabData` should use hardcoded demo data (or empty slices) for Phase 17 since no real data layer exists. The spec says "restyling only — no new routes or data models." + +### Pattern 5: Day Separator Logic + +`PlanningShowDaySeparator(events []PlanningEventRow, index int) bool`: +```go +func PlanningShowDaySeparator(events []PlanningEventRow, index int) bool { + if index == 0 { + return true + } + return events[index].DateLabel != events[index-1].DateLabel +} +``` + +Usage in templ: +```go +for i, event := range data.Events { + if PlanningShowDaySeparator(data.Events, i) { +
+ { event.DateLabel } +
+ } + @PlanningEventListItem(event) +} +``` + +### Anti-Patterns to Avoid + +- **Hardcoded hex in CSS:** All new `.message-*` rules must use `var(--...)` tokens — except the brand tint `rgba(128, 78, 236, 0.10)` which is spelled out by design (token wrapping would add indirection without benefit for a single-use value). +- **Inline Tailwind in CSS:** Do not add Tailwind utility classes inside the `app.css` file. The day separator uses Tailwind classes in the `.templ` file directly (matching `DiscussionDaySeparator` which also uses Tailwind inline). +- **Modifying handler signature beyond what's needed:** The handler currently calls `views.PlanningMainContent()` and `views.ChatMainContent()` with no arguments. Adding a data parameter requires updating the handler call — that's acceptable. Do NOT add `currentUserID` or auth context to these handlers in Phase 17 — the spec says no SSE or POST handler, no real data source. +- **Breaking existing tests:** `router_test.go` tests `/planning` and `/chat` routes only indirectly (via the sidebar nav strings "Planning" and "Discussions"). Tests check for the sidebar nav item text, not for page-specific content. Adding real content to these views will not break existing tests as long as the route still returns 200 and renders the sidebar correctly. + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| Card surface | Custom div with border/radius styles | `.ui-card` class on `#discussion-messages` | Already defined in `card.css` with correct shadow/border tokens | +| Empty state | Custom div with icon + text | `@ui.EmptyState(ui.EmptyStateProps{...})` | Component exists in `empty_state.templ`; used in tablos and tasks pages | +| Section heading layout | Custom flex div | `.overview-section` + `.overview-section-heading` | Already in `app.css` lines 875-892; battle-tested in dashboard overview | +| CSS token lookup | Hardcoded values | `var(--color-brand-primary)` etc. | All tokens verified in `base.css`; design tokens guarantee future dark-mode compatibility | + +## Common Pitfalls + +### Pitfall 1: Wrong File Paths from CONTEXT.md + +**What goes wrong:** CONTEXT.md references `backend/templates/discussion.templ`, `backend/internal/web/handlers_discussion.go`, `backend/templates/planning_forms.go`. These paths do not exist. +**Why it happens:** CONTEXT.md was written referencing a backend structure from an earlier planning artifact (`13-PATTERNS.md` shows `backend/` was the original path). The rewrite settled in `go-backend/`. +**How to avoid:** All file edits go to `go-backend/internal/web/views/` and `go-backend/internal/web/ui/app.css`. Do not create files at the `backend/` paths. +**Warning signs:** `os.Stat` or `find` on `backend/templates/` returns nothing. + +### Pitfall 2: DiscussionMessageFromRow / SSE Path Is Not In Scope + +**What goes wrong:** Implementing the SSE stream handler described in D-D03/D-D04 of CONTEXT.md. The `/chat` route is a simple GET handler — there is no POST, no SSE, no `DiscussionMessageCreateHandler`. +**Why it happens:** CONTEXT.md was written for a richer existing codebase. In this codebase, the chat is static/placeholder. +**How to avoid:** Phase 17 only replaces the `ChatMainContent()` stub with a real static view. If demo messages are needed, use hardcoded data like `overviewTasks()`. The SSE path does not exist. +**Warning signs:** Searching for `DiscussionMessageCreateHandler` or `renderDiscussionMessageHTML` returns zero results. + +### Pitfall 3: `.overview-section-heading` Only Styles h3 + +**What goes wrong:** Using `

Planning

` inside `.overview-section-heading` produces unstyled heading text because the CSS rule targets `h3` only. +**Why it happens:** The dashboard uses `h3` for section headings. The planning page spec uses `h1`. +**How to avoid:** Add `h1` to the `.overview-section-heading` selector in `app.css`, OR apply typography inline. The clean solution is extending the CSS selector. +**Warning signs:** Planning heading renders in browser default `h1` size with no weight/color override. + +### Pitfall 4: Handler Signature Update Required + +**What goes wrong:** `views.PlanningMainContent()` and `views.ChatMainContent()` currently take zero arguments. After Phase 17, they take a view model parameter. The handler in `auth.go` must be updated to match. +**Why it happens:** Go is strictly typed — the compiler will fail if the call site doesn't match the function signature. +**How to avoid:** Update `handlers/auth.go` `GetPlanningPage()` and `GetChatPage()` to construct and pass the view model. This is the only required handler change. +**Warning signs:** `go build ./...` fails with "too many arguments in call." + +### Pitfall 5: Tailwind CSS Purge May Miss New Classes + +**What goes wrong:** The day separator uses Tailwind classes like `bg-slate-50 text-slate-500`. If the Tailwind CSS build doesn't scan `dashboard_components.templ`, these classes may not appear in `tailwind.css`. +**Why it happens:** Tailwind scans content globs — if new classes appear only in the new templ file and the glob already covers all `.templ` files, they will be included. Verify the existing Tailwind config includes `.templ` files. +**How to avoid:** Check the Tailwind config content glob. Existing pages already use `bg-slate-100`, `text-slate-500` etc. — if these classes are already in `tailwind.css`, the day separator classes will be too (they were used in the existing `DiscussionDaySeparator` which the CONTEXT says already exists as a pattern). +**Warning signs:** Day separator renders with no background color in the browser. + +### Pitfall 6: Test Assertions on "Planning" and "Discussions" Sidebar Strings + +**What goes wrong:** `router_test.go` line 211–212 asserts the home page body contains the strings `"Planning"` and `"Discussions"`. These are sidebar nav item labels, not page content — they will remain present as long as the sidebar renders correctly. No risk here. +**Why it matters:** Confirms existing tests are not fragile to the content changes being made. + +## Code Examples + +### New CSS block for app.css +```css +/* Source: 17-UI-SPEC.md (VERIFIED — verified against base.css tokens) */ + +.message-row { + padding: 0.75rem 1rem; +} + +.message-own { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.message-other { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.message-bubble { + border: 1px solid var(--color-border-subtle); + border-radius: 0.25rem 0.75rem 0.75rem 0.75rem; + max-width: 70%; + padding: 0.75rem 1rem; + white-space: pre-wrap; + word-break: break-words; +} + +.message-own .message-bubble { + background: rgba(128, 78, 236, 0.10); + border-radius: 0.75rem 0.75rem 0.25rem 0.75rem; +} + +.message-other .message-bubble { + background: var(--color-surface-default); +} + +.message-meta { + display: flex; + gap: 0.5rem; + margin-bottom: 0.25rem; + font-size: 0.875rem; + color: var(--color-text-muted); +} + +.message-own .message-meta { + justify-content: flex-end; +} + +.message-meta .message-author { + font-weight: 600; +} +``` + +### Heading selector extension needed in app.css +```css +/* Source: go-backend/internal/web/ui/app.css line 886 (VERIFIED — existing rule) */ +/* EXTEND to add h1: */ +.overview-section-heading h1, +.overview-section-heading h3, +.tasks-section-header h3 { + color: var(--color-surface-muted-inverse); + font-size: 1.6rem; + font-weight: 600; + margin: 0; +} +``` + +### DiscussionTabData view model +```go +// Source: home.go pattern (VERIFIED) — adapted for discussion +// File: go-backend/internal/web/views/discussion_view.go +package views + +type DiscussionMessageView struct { + Author string + Timestamp string + Body string + IsOwn bool +} + +type DiscussionTabData struct { + Messages []DiscussionMessageView +} + +func NewDiscussionTabData() DiscussionTabData { + return DiscussionTabData{Messages: []DiscussionMessageView{}} +} +``` + +### PlanningTabData view model +```go +// Source: home.go pattern (VERIFIED) — adapted for planning +// File: go-backend/internal/web/views/planning_view.go +package views + +type PlanningEventRow struct { + DateLabel string + TimeRange string + Title string + TabloTitle string + Location string +} + +type PlanningTabData struct { + Events []PlanningEventRow + DateRange string // e.g. "May 17 – May 30, 2026" +} + +func NewPlanningTabData() PlanningTabData { + return PlanningTabData{Events: []PlanningEventRow{}} +} + +func PlanningShowDaySeparator(events []PlanningEventRow, index int) bool { + if index == 0 { + return true + } + return events[index].DateLabel != events[index-1].DateLabel +} +``` + +## Runtime State Inventory + +Not applicable — this is a greenfield view addition, not a rename/refactor/migration phase. No stored data, live service config, OS state, secrets, or build artifacts require updating. + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| `AppSectionMainContent(...)` stub | Real `ChatMainContent(data)` / `PlanningMainContent(data)` | Phase 17 | Pages go from placeholder to styled content | +| Raw inline Tailwind classes on containers | `.ui-card` design system class | Phase 13 (established) | Consistent border/shadow/bg across all surfaces | +| `h3`-only heading selector | `h1, h3` in `.overview-section-heading` | Phase 17 (minor extension) | Planning heading styled correctly | + +## Assumptions Log + +| # | Claim | Section | Risk if Wrong | +|---|-------|---------|---------------| +| A1 | No real discussion or planning data model exists in the go-backend — both pages use placeholder/demo data for Phase 17 | Architecture Patterns | Low — confirmed by grep showing zero `DiscussionMessageView`/`PlanningEventRow` types in go-backend | +| A2 | The Tailwind config content glob already covers `.templ` files in `go-backend/internal/web/views/` | Common Pitfalls — Pitfall 5 | Low — Tailwind is already generating classes used in existing views; new Tailwind classes in new views will be included if the glob covers the directory | +| A3 | `router_test.go` test assertions on "Planning" and "Discussions" are sidebar-only checks that will not be broken by adding view content | Common Pitfalls — Pitfall 6 | Low — read the test: the assertions fire on the home page response which includes the sidebar nav, not the planning/chat page content | + +## Open Questions + +1. **Should ChatMainContent and PlanningMainContent use demo/hardcoded data or empty slices?** + - What we know: The spec says "restyling only" with no new data models. The existing pattern (tasks, dashboard) uses hardcoded demo data in `.go` helper files. + - What's unclear: Whether the acceptance criterion "browser walkthrough confirming visual consistency" requires real data or if empty-state UI is sufficient. + - Recommendation: Implement both an empty-state (via `@ui.EmptyState`) and a small set of demo messages/events (3-5 items) so the visual design is demonstrable without a database. The planner should create a task for each. + +2. **Is `author + timestamp` above the bubble the right location given the planning context?** + - What we know: D-M03 says "Author email + timestamp appear ABOVE the bubble text." + - What's unclear: For a standalone chat page (not a tablo-specific discussion), the author field may always be the current user or a static demo user. + - Recommendation: Implement as specified. Use `"you"` or `"demo@xtablo.com"` as demo author strings. + +## Environment Availability + +| Dependency | Required By | Available | Version | Fallback | +|------------|------------|-----------|---------|----------| +| Go compiler | `go build`, `go test` | ✓ | 1.26.0 (go.mod) | — | +| templ CLI | `templ generate` | ✓ | current (used in previous phases) | — | +| `go test ./...` | REQ-6 acceptance gate | ✓ | passes clean (verified) | — | + +## Validation Architecture + +### Test Framework +| Property | Value | +|----------|-------| +| Framework | Go standard `testing` package + httptest | +| Config file | none — standard `go test` | +| Quick run command | `cd go-backend && go test ./... -count=1` | +| Full suite command | `cd go-backend && go test ./... -count=1` | + +### Phase Requirements → Test Map + +| Req ID | Behavior | Test Type | Automated Command | File Exists? | +|--------|----------|-----------|-------------------|-------------| +| CHAT-UI-01 | Discussion container uses `.ui-card`; own messages get `.message-own`; others get `.message-other` | unit (render test) | `go test ./internal/web/views/ -run TestChat -count=1` | ❌ Wave 0 | +| PLAN-UI-01 | Planning heading uses `.overview-section`; events grouped under date separators; `PlanningShowDaySeparator` returns correct bool | unit (render test + logic test) | `go test ./internal/web/views/ -run TestPlanning -count=1` | ❌ Wave 0 | +| REQ-6 | All existing handler tests pass unchanged | regression | `cd go-backend && go test ./... -count=1` | ✅ | + +### Sampling Rate +- **Per task commit:** `cd go-backend && go test ./internal/web/views/ -count=1` +- **Per wave merge:** `cd go-backend && go test ./... -count=1` +- **Phase gate:** Full suite green before `/gsd-verify-work` + +### Wave 0 Gaps + +- [ ] `go-backend/internal/web/views/discussion_view_test.go` — covers CHAT-UI-01 (render assertions: `.ui-card`, `.message-own`, `.message-other`, `.message-bubble`) +- [ ] `go-backend/internal/web/views/planning_view_test.go` — covers PLAN-UI-01 (day separator logic + render assertions: `.overview-section`, `.overview-section-heading`, separator div) + +Pattern for render tests (follow `dashboard_components_test.go`): +```go +func TestChatMainContentRendersBubbleClasses(t *testing.T) { + data := DiscussionTabData{Messages: []DiscussionMessageView{ + {Author: "me@test.com", Body: "Hello", IsOwn: true}, + {Author: "other@test.com", Body: "World", IsOwn: false}, + }} + html := renderViewToString(t, ChatMainContent(data)) + for _, want := range []string{`class="ui-card"`, `message-own`, `message-other`, `message-bubble`} { + if !strings.Contains(html, want) { + t.Fatalf("expected %q in rendered chat", want) + } + } +} +``` + +## Security Domain + +Security enforcement applies. ASVS categories assessed below: + +### Applicable ASVS Categories + +| ASVS Category | Applies | Standard Control | +|---------------|---------|-----------------| +| V2 Authentication | No | Auth is already handled by `authenticatedUser()` in all protected handlers | +| V3 Session Management | No | Session handling is unchanged — `renderAppPage` enforces auth | +| V4 Access Control | No | No new routes or permissions added | +| V5 Input Validation | No | No new user input accepted in Phase 17 (display-only) | +| V6 Cryptography | No | No cryptographic operations | + +### Known Threat Patterns + +| Pattern | STRIDE | Standard Mitigation | +|---------|--------|---------------------| +| XSS via message body rendering | Tampering | templ auto-escapes all `{ variable }` interpolations — do not use `templ.Raw()` for message bodies | + +**Key note:** templ's `{ expr }` syntax HTML-escapes by default. `templ.Raw()` bypasses escaping and must not be used for user-supplied content (message bodies, author names). [ASSUMED — based on templ documentation behavior; verified by convention in existing codebase] + +## Sources + +### Primary (HIGH confidence) +- `go-backend/internal/web/views/dashboard_components.templ` — existing `overview-section` pattern, `PlanningMainContent`/`ChatMainContent` stubs, `AppSectionMainContent` [VERIFIED: read] +- `go-backend/internal/web/views/home.go` — demo data pattern (`overviewTasks`, `overviewQuickActions`) [VERIFIED: read] +- `go-backend/internal/web/ui/app.css` — `.overview-section` at lines 875-892 [VERIFIED: read] +- `go-backend/internal/web/ui/card.css` — `.ui-card` definition [VERIFIED: read] +- `go-backend/internal/web/ui/base.css` — all color tokens [VERIFIED: read] +- `go-backend/internal/web/ui/empty_state.templ` — `@ui.EmptyState` API [VERIFIED: read] +- `go-backend/router_test.go` — existing test assertions [VERIFIED: read] +- `go-backend/internal/web/views/dashboard_components_test.go` — `renderViewToString` helper pattern [VERIFIED: read] +- `go-backend/internal/web/handlers/auth.go` — `GetPlanningPage`, `GetChatPage` handlers [VERIFIED: read] + +### Secondary (MEDIUM confidence) +- `.planning/phases/17-chat-planning/17-CONTEXT.md` — locked design decisions (D-M01 through D-P04) +- `.planning/phases/17-chat-planning/17-UI-SPEC.md` — CSS class specifications, color values, typography +- `.planning/phases/17-chat-planning/17-SPEC.md` — 6 locked requirements, acceptance criteria + +## Metadata + +**Confidence breakdown:** +- Standard stack: HIGH — no new dependencies; verified all existing components +- Architecture: HIGH — codebase fully read; pattern is clear from existing pages +- Pitfalls: HIGH — file path mismatch and missing SSE handler are confirmed by grep +- Test strategy: HIGH — existing test infrastructure is the exact model to follow + +**Research date:** 2026-05-17 +**Valid until:** 2026-06-17 (stable Go/templ stack)