docs(17): research phase domain

This commit is contained in:
Arthur Belleville 2026-05-17 06:37:06 +02:00
parent b84503a54a
commit f45f7c6010
No known key found for this signature in database

View file

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