# 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>
## 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 `<section class="overview-section">` + `<div class="overview-section-heading">` with `<h1>Planning</h1>`.
- D-P02: Add `PlanningShowDaySeparator(events []PlanningEventRow, index int) bool` helper.
- D-P03: Day separator element: `<div class="bg-slate-50 px-4 py-2 text-center text-sm text-slate-500">`.
- 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
</user_constraints>

<phase_requirements>
## 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 |
</phase_requirements>

## 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 `<head>` 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)
<section id="overview-projects-section" class="overview-section">
    <div class="overview-section-heading">
        <h3>Mes Projets</h3>
    </div>
    ...
</section>
```

Planning variant — use `<section class="overview-section">` + `<div class="overview-section-heading">` with `<h1>Planning</h1>` 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 `<h1>`. 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) {
        <div class="bg-slate-50 px-4 py-2 text-center text-sm text-slate-500">
            { event.DateLabel }
        </div>
    }
    @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 `<h1>Planning</h1>` 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 (RESOLVED)

1. **Should ChatMainContent and PlanningMainContent use demo/hardcoded data or empty slices?**
   RESOLVED: Use hardcoded demo data (3–4 items) matching the `home.go` pattern. Phase 17 is restyle-only; real data threading (D-D01–D-D04) is deferred to a future phase when the chat backend exists in go-backend/.

2. **Is `author + timestamp` above the bubble the right location given the planning context?**
   RESOLVED: Implemented as specified per D-M03. Demo author strings use `"you"` / `"demo@xtablo.com"` for the static data set.

## 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)
