with the day-separated event list using a for loop:
+ for i, event := range data.Events {
+ if PlanningShowDaySeparator(data.Events, i) {
+
or similar simple row containing:
+ - TimeRange:
{ event.TimeRange }
+ - Title:
{ event.Title }
+ - TabloTitle + Location right column:
{ event.TabloTitle } if event.Location != "" then " · " + event.Location
+ DateLabel is NOT rendered inside the event row — it appears only in the day separator (per D-P04).
+ }
+
+ Important: Do NOT use templ.Raw() for any field. All content via { expr }.
+
+ Step 2 — Update GetPlanningPage in go-backend/internal/web/handlers/auth.go:
+ Change `return views.PlanningMainContent()` to `return views.PlanningMainContent(views.NewPlanningTabData())`.
+ No other handler logic changes.
+
+ Step 3 — Run templ generate:
+ Execute `cd go-backend && templ generate` (or `go run github.com/a-h/templ/cmd/templ generate`) to regenerate dashboard_components_templ.go.
+
+ Step 4 — Run tests (GREEN confirmation):
+ `cd go-backend && go test ./internal/web/views/ -run TestPlanning -count=1` — must exit 0.
+ `cd go-backend && go test ./... -count=1` — must exit 0 (full regression gate).
+
+
+
+ cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/go-backend && go test ./... -count=1 2>&1
+
+
+
+ - dashboard_components.templ line containing "templ PlanningMainContent" reads "templ PlanningMainContent(data PlanningTabData)" (not zero-arg)
+ - handlers/auth.go GetPlanningPage contains "views.NewPlanningTabData()" in the call
+ - `go build ./...` exits 0 from go-backend/
+ - `go test ./internal/web/views/ -run TestPlanning -count=1` exits 0 with output "PASS"
+ - `go test ./... -count=1` exits 0
+ - The rendered PlanningMainContent HTML contains "overview-section" (proven by TestPlanningMainContentRendersOverviewSection)
+ - The rendered PlanningMainContent HTML contains "bg-slate-50" (day separator element present)
+ - templ generate produced an updated dashboard_components_templ.go
+
+
+
PlanningMainContent wired with real view, tests GREEN, full suite passes
+
+
+
+ Task 3: Browser verify — planning view visual result + combined walkthrough
+
+ - .overview-section-heading h1 selector extension in app.css (planning h1 now styled at 1.6rem 600 weight)
+ - PlanningMainContent() component with overview-section heading, day-separated event list, empty state via @ui.EmptyState
+ - Handler wired with demo data (5 events across 2 dates)
+
+
+ 1. Start the dev server: `cd go-backend && go run ./cmd/web` (or `just dev`)
+ 2. Navigate to http://localhost:8080/planning (sign in first if redirected)
+ 3. Confirm the "Planning" heading: large bold text (1.6rem, 600 weight) in the overview-section-heading style — visually matches section headings on the dashboard and tablo-detail pages
+ 4. Confirm the date range label appears to the right of the heading (or below, depending on heading layout)
+ 5. Confirm 2 date separator bars appear: one reading "May 17, 2026" and one reading "May 18, 2026", each with the gray-tinted strip appearance (bg-slate-50)
+ 6. Confirm 3 events appear under the first separator and 2 events under the second
+ 7. Confirm no DateLabel text appears inside the individual event rows
+ 8. Navigate to http://localhost:8080/chat (if Plan 01 has been executed — or verify Plan 01 is also complete)
+ 9. Compare both pages side-by-side against the tablo detail page (/tablos/[any-tablo-id]) — confirm visual consistency: card surfaces, heading styles, and section layout match across all three surfaces
+
+ Type "approved" to continue, or describe any visual issues to fix
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| template render → HTML output | templ { expr } interpolations for Title, TimeRange, TabloTitle, Location, DateRange fields |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Disposition | Mitigation Plan |
+|-----------|----------|-----------|-------------|-----------------|
+| T-17-02-01 | Tampering | planning_view.go — event field strings rendered in template | mitigate | Use templ { expr } syntax (auto-escapes HTML) — never templ.Raw() for any PlanningEventRow fields. Demo data is hardcoded strings; future real data will flow through the same escaping path. |
+| T-17-02-02 | Information Disclosure | /planning route serving demo data | accept | Phase 17 serves hardcoded demo events only — no real user data. Auth is enforced by renderAppPage → authenticatedUser() — unchanged from existing handler. |
+
+
+
+Logic gate: `cd go-backend && go test ./internal/web/views/ -run TestPlanningShowDaySeparator -count=1` exits 0.
+Render gate: `cd go-backend && go test ./internal/web/views/ -run TestPlanningMainContentRendersOverviewSection -count=1` exits 0.
+Full regression gate: `cd go-backend && go test ./... -count=1` exits 0.
+Build check: `cd go-backend && go build ./...` exits 0.
+
+
+
+- ".overview-section-heading h1," added to the selector in app.css (h1 now inherits 1.6rem 600 weight heading style)
+- planning_view.go exports PlanningEventRow, PlanningTabData, NewPlanningTabData (5 demo events across 2 dates), PlanningShowDaySeparator
+- PlanningMainContent(data PlanningTabData) renders .overview-section heading + day separators + event rows without DateLabel in row body
+- handlers/auth.go GetPlanningPage passes views.NewPlanningTabData() to views.PlanningMainContent()
+- go test ./... -count=1 exits 0
+- Browser: planning heading styled to match dashboard section headings; 2 date separators visible; events grouped correctly
+
+
+
diff --git a/.planning/phases/17-chat-planning/17-PATTERNS.md b/.planning/phases/17-chat-planning/17-PATTERNS.md
new file mode 100644
index 0000000..fd039a1
--- /dev/null
+++ b/.planning/phases/17-chat-planning/17-PATTERNS.md
@@ -0,0 +1,470 @@
+# Phase 17: Chat & Planning - Pattern Map
+
+**Mapped:** 2026-05-17
+**Files analyzed:** 6 new/modified files
+**Analogs found:** 6 / 6
+
+## File Classification
+
+| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
+|---|---|---|---|---|
+| `go-backend/internal/web/views/discussion_view.go` | model/view-helper | transform | `go-backend/internal/web/views/home.go` | exact |
+| `go-backend/internal/web/views/planning_view.go` | model/view-helper | transform | `go-backend/internal/web/views/home.go` | exact |
+| `go-backend/internal/web/views/dashboard_components.templ` | component | request-response | self (existing stubs being replaced) | exact |
+| `go-backend/internal/web/views/discussion_view_test.go` | test | — | `go-backend/internal/web/views/dashboard_components_test.go` | exact |
+| `go-backend/internal/web/views/planning_view_test.go` | test | — | `go-backend/internal/web/views/dashboard_components_test.go` | exact |
+| `go-backend/internal/web/ui/app.css` | config/style | — | self (existing `.overview-section` block at line 875) | exact |
+| `go-backend/internal/web/handlers/auth.go` | controller | request-response | self (existing `GetPlanningPage` / `GetChatPage` at lines 123–133) | exact |
+
+---
+
+## Pattern Assignments
+
+### `go-backend/internal/web/views/discussion_view.go` (model, transform)
+
+**Analog:** `go-backend/internal/web/views/home.go`
+
+**Package and imports pattern** (lines 1–13):
+```go
+package views
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ tablomodel "xtablo-backend/internal/tablos"
+ "xtablo-backend/internal/web/dates"
+ "xtablo-backend/internal/web/tabloicons"
+
+ "github.com/a-h/templ"
+)
+```
+For `discussion_view.go` no external imports are needed beyond `package views` — the struct fields are all plain `string` and `bool`.
+
+**Struct + constructor pattern** (home.go lines 61–68 for `dashboardTask`, adapted):
+```go
+type dashboardTask struct {
+ Title string
+ Project string
+ ProjectKey string
+ ProjectHue string
+ Date string
+ Status string
+ StatusTone string
+ Completed bool
+}
+```
+Apply same pattern: named struct with plain value fields, constructor returning a populated slice.
+
+**Demo data factory pattern** (home.go lines 100–110):
+```go
+func overviewTasks() []dashboardTask {
+ return []dashboardTask{
+ {Title: "yo", Project: "Hello", ProjectKey: "H", ProjectHue: "blue", ...},
+ ...
+ }
+}
+```
+
+**New file to create** — copy the struct + factory pattern:
+```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{
+ // 3-5 hardcoded demo messages alternating IsOwn true/false
+ }}
+}
+```
+
+---
+
+### `go-backend/internal/web/views/planning_view.go` (model, transform)
+
+**Analog:** `go-backend/internal/web/views/home.go`
+
+Same struct + factory pattern as `discussion_view.go`. Also contains the `PlanningShowDaySeparator` helper:
+
+**Day separator helper pattern** (pure logic, no analog — implement fresh):
+```go
+func PlanningShowDaySeparator(events []PlanningEventRow, index int) bool {
+ if index == 0 {
+ return true
+ }
+ return events[index].DateLabel != events[index-1].DateLabel
+}
+```
+
+**New file to create:**
+```go
+package views
+
+type PlanningEventRow struct {
+ DateLabel string
+ TimeRange string
+ Title string
+ TabloTitle string
+ Location string
+}
+
+type PlanningTabData struct {
+ Events []PlanningEventRow
+ DateRange string
+}
+
+func NewPlanningTabData() PlanningTabData {
+ return PlanningTabData{Events: []PlanningEventRow{
+ // 3-5 hardcoded demo events spanning 2 dates
+ }}
+}
+
+func PlanningShowDaySeparator(events []PlanningEventRow, index int) bool {
+ if index == 0 {
+ return true
+ }
+ return events[index].DateLabel != events[index-1].DateLabel
+}
+```
+
+---
+
+### `go-backend/internal/web/views/dashboard_components.templ` — `ChatMainContent` and `PlanningMainContent` stubs (component, request-response)
+
+**Analog:** `OverviewProjectsSection` in `go-backend/internal/web/views/dashboard_components.templ` (lines 238–257)
+
+**Overview-section heading pattern** (lines 238–256):
+```go
+templ OverviewProjectsSection(projects []TabloCardView) {
+
+
+
Mes Projets
+
+
+ for _, project := range visibleOverviewProjects(projects) {
+ @TabloGridCard(project)
+ }
+
+ ...
+
+}
+```
+Planning heading uses the same `
` + `` wrapper. Replace `
` with `` (requires extending the CSS selector — see Shared Patterns below). Add date-range label and nav buttons as right-side content inside `.overview-section-heading`.
+
+**ui-card usage pattern** (card.css, lines 1–6):
+```css
+.ui-card {
+ background: var(--color-surface-default);
+ border: 1px solid var(--color-border-default);
+ border-radius: 1rem;
+ box-shadow: var(--shadow-surface-md);
+}
+```
+Apply `class="ui-card"` to the `#discussion-messages` container div — no inline border/bg classes needed.
+
+**EmptyState usage pattern** (`go-backend/internal/web/views/tablos.templ` lines 95–112):
+```go
+@ui.EmptyState(ui.EmptyStateProps{
+ Title: "Aucun projet trouvé",
+ Description: "Créez votre premier projet",
+ Icon: ui.UIIcon("grid3x3"),
+ Action: ui.Button(ui.ButtonProps{
+ Label: "Nouveau projet",
+ Variant: ui.ButtonVariantDefault,
+ Size: ui.SizeMD,
+ Type: "button",
+ Icon: "plus",
+ ...
+ }),
+})
+```
+For planning empty state: no `Action` needed — omit that field. For chat empty state: also no `Action`.
+
+**Current stubs being replaced** (dashboard_components.templ lines 162–168):
+```go
+templ PlanningMainContent() {
+ @AppSectionMainContent("Planning", "Visualisez le rythme...")
+}
+
+templ ChatMainContent() {
+ @AppSectionMainContent("Discussions", "Retrouvez les conversations...")
+}
+```
+Replace with real components accepting a data parameter:
+```go
+templ PlanningMainContent(data PlanningTabData) { ... }
+templ ChatMainContent(data DiscussionTabData) { ... }
+```
+
+**For-range with conditional separator pattern** — closest analog is the tasks list in `OverviewTasks` (lines 318–336), but the day-separator variant is new. Use `for i, event := range data.Events` with `if PlanningShowDaySeparator(data.Events, i)` preceding each row.
+
+---
+
+### `go-backend/internal/web/handlers/auth.go` — `GetPlanningPage` and `GetChatPage` (controller, request-response)
+
+**Analog:** `go-backend/internal/web/handlers/auth.go` lines 123–133 (the stubs being updated)
+
+**renderAppPage helper pattern** (lines 178–206):
+```go
+func (h *AuthHandler) renderAppPage(activePath string, content func(user PublicUser) templ.Component) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ user, ok := h.authenticatedUser(r.Context(), r)
+ if !ok {
+ http.Redirect(w, r, "/login", http.StatusSeeOther)
+ return
+ }
+ tablos, err := h.repo.ListTablos(r.Context(), ListTablosInput{OwnerID: user.ID})
+ if err != nil {
+ http.Error(w, "failed to load projects", http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ pageContent := content(user)
+ var renderErr error
+ if isHXRequest(r) {
+ renderErr = views.DashboardContentSwap(activePath, tablos, pageContent).Render(r.Context(), w)
+ } else {
+ renderErr = views.DashboardPage(activePath, tablos, pageContent).Render(r.Context(), w)
+ }
+ if renderErr != nil {
+ http.Error(w, "failed to render app page", http.StatusInternalServerError)
+ }
+ }
+}
+```
+
+**Current call sites to update** (lines 123–133):
+```go
+func (h *AuthHandler) GetPlanningPage() http.HandlerFunc {
+ return h.renderAppPage("/planning", func(user PublicUser) templ.Component {
+ return views.PlanningMainContent() // <-- add views.NewPlanningTabData()
+ })
+}
+
+func (h *AuthHandler) GetChatPage() http.HandlerFunc {
+ return h.renderAppPage("/chat", func(user PublicUser) templ.Component {
+ return views.ChatMainContent() // <-- add views.NewDiscussionTabData()
+ })
+}
+```
+After Phase 17 the `user PublicUser` parameter is available but not used — that is fine, consistent with how `GetFilesPage` and `GetFeedbackPage` also ignore the user (lines 135–143).
+
+---
+
+### `go-backend/internal/web/views/discussion_view_test.go` (test)
+
+**Analog:** `go-backend/internal/web/views/dashboard_components_test.go`
+
+**renderViewToString helper** (lines 143–151) — already defined in the same package; do NOT redefine it:
+```go
+func renderViewToString(t *testing.T, component templ.Component) string {
+ t.Helper()
+ var buf bytes.Buffer
+ if err := component.Render(context.Background(), &buf); err != nil {
+ t.Fatalf("render component: %v", err)
+ }
+ return buf.String()
+}
+```
+
+**Render assertion pattern** (lines 44–65):
+```go
+func TestOverviewProjectsSectionRendersColorAndEditAction(t *testing.T) {
+ record := tablomodel.Record{ ... }
+ html := renderViewToString(t, OverviewProjectsSection(OverviewProjectsFromTablos([]tablomodel.Record{record})))
+ for _, want := range []string{
+ `style="--project-color:#22C55E;"`,
+ `aria-label="Modifier le projet"`,
+ } {
+ if !strings.Contains(html, want) {
+ t.Fatalf("expected %q in %q", want, html)
+ }
+ }
+}
+```
+
+**Package declaration** (line 1): `package views` — same package as the components under test.
+
+**Imports pattern** (lines 3–13):
+```go
+import (
+ "bytes"
+ "context"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/a-h/templ"
+ "github.com/google/uuid"
+ tablomodel "xtablo-backend/internal/tablos"
+)
+```
+For discussion and planning tests, the minimal imports are: `"bytes"`, `"context"`, `"strings"`, `"testing"`.
+
+---
+
+### `go-backend/internal/web/views/planning_view_test.go` (test)
+
+Same pattern as `discussion_view_test.go` above. Additionally needs a logic test for `PlanningShowDaySeparator`:
+
+```go
+func TestPlanningShowDaySeparator(t *testing.T) {
+ events := []PlanningEventRow{
+ {DateLabel: "May 17, 2026", ...},
+ {DateLabel: "May 17, 2026", ...},
+ {DateLabel: "May 18, 2026", ...},
+ }
+ // index 0 → true (always)
+ // index 1 → false (same date as 0)
+ // index 2 → true (date change)
+}
+```
+
+---
+
+### `go-backend/internal/web/ui/app.css` — new `.message-*` block and selector extension (config/style)
+
+**Analog:** `.overview-section` block (app.css lines 875–892) — token-only CSS, no hardcoded hex
+
+**Existing heading selector to extend** (lines 886–892):
+```css
+.overview-section-heading h3,
+.tasks-section-header h3 {
+ color: var(--color-surface-muted-inverse);
+ font-size: 1.6rem;
+ font-weight: 600;
+ margin: 0;
+}
+```
+Add `h1` to this rule:
+```css
+.overview-section-heading h1,
+.overview-section-heading h3,
+.tasks-section-header h3 { ... }
+```
+
+**New CSS block to append at end of file** (after line 1897):
+```css
+/* ── Message bubbles ── */
+
+.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-word;
+}
+
+.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;
+}
+```
+
+---
+
+## Shared Patterns
+
+### Token-only CSS rule
+**Source:** `go-backend/internal/web/ui/app.css` lines 875–892, `go-backend/internal/web/ui/base.css`
+**Apply to:** All new `.message-*` rules in `app.css`
+
+All `var(--...)` token names verified in `base.css`:
+- `--color-brand-primary` (for rgba tint value `128, 78, 236`)
+- `--color-surface-default`
+- `--color-border-subtle`
+- `--color-text-muted`
+- `--color-surface-muted-inverse`
+- `--shadow-surface-md`
+
+Exception: the rgba brand tint `rgba(128, 78, 236, 0.10)` is spelled out intentionally (single-use value; wrapping in a token adds indirection without benefit).
+
+### templ auto-escaping
+**Source:** Project-wide convention; verified in all existing templ files
+**Apply to:** All `{ variable }` interpolations in message templates (Author, Body, Timestamp)
+
+Use `{ expr }` — never `templ.Raw()` for user-supplied or demo data fields.
+
+### HTMX page swap pattern
+**Source:** `go-backend/internal/web/handlers/auth.go` lines 196–204
+**Apply to:** `GetPlanningPage` and `GetChatPage` handler updates
+
+```go
+if isHXRequest(r) {
+ renderErr = views.DashboardContentSwap(activePath, tablos, pageContent).Render(r.Context(), w)
+} else {
+ renderErr = views.DashboardPage(activePath, tablos, pageContent).Render(r.Context(), w)
+}
+```
+No changes to this branching logic — only the `content(user)` call site changes to pass a view model.
+
+### Day separator visual style
+**Source:** `CONTEXT.md` D-P03 (no existing analog to copy from — use inline Tailwind directly in the `.templ` file, consistent with other Tailwind-in-templ usage across the codebase):
+```go
+
+ { event.DateLabel }
+
+```
+These Tailwind classes (`bg-slate-50`, `text-slate-500`) already exist in the generated `tailwind.css` (used by existing pages), so no purge issue.
+
+---
+
+## No Analog Found
+
+All files have close analogs. No entries needed here.
+
+---
+
+## Metadata
+
+**Analog search scope:** `go-backend/internal/web/views/`, `go-backend/internal/web/ui/`, `go-backend/internal/web/handlers/`
+**Files scanned:** 7 (dashboard_components.templ, home.go, dashboard_components_test.go, auth.go, card.css, empty_state.templ, app.css)
+**Pattern extraction date:** 2026-05-17
diff --git a/.planning/phases/17-chat-planning/17-VALIDATION.md b/.planning/phases/17-chat-planning/17-VALIDATION.md
new file mode 100644
index 0000000..bda777b
--- /dev/null
+++ b/.planning/phases/17-chat-planning/17-VALIDATION.md
@@ -0,0 +1,75 @@
+---
+phase: 17
+slug: chat-planning
+status: draft
+nyquist_compliant: false
+wave_0_complete: false
+created: 2026-05-17
+---
+
+# Phase 17 — Validation Strategy
+
+> Per-phase validation contract for feedback sampling during execution.
+
+---
+
+## Test Infrastructure
+
+| Property | Value |
+|----------|-------|
+| **Framework** | go test |
+| **Config file** | go-backend/go.mod |
+| **Quick run command** | `cd go-backend && go test ./internal/web/... -count=1` |
+| **Full suite command** | `cd go-backend && go test ./... -count=1` |
+| **Estimated runtime** | ~5 seconds |
+
+---
+
+## Sampling Rate
+
+- **After every task commit:** Run `cd go-backend && go test ./internal/web/... -count=1`
+- **After every plan wave:** Run `cd go-backend && go test ./... -count=1`
+- **Before `/gsd-verify-work`:** Full suite must be green
+- **Max feedback latency:** 10 seconds
+
+---
+
+## Per-Task Verification Map
+
+| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
+|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
+| 17-01-01 | 01 | 1 | CHAT-UI-01 | — | N/A (UI-only) | unit | `cd go-backend && go test ./internal/web/... -count=1` | ✅ | ⬜ pending |
+| 17-01-02 | 01 | 1 | CHAT-UI-01 | — | N/A (UI-only) | unit | `cd go-backend && go test ./internal/web/... -count=1` | ✅ | ⬜ pending |
+| 17-02-01 | 02 | 1 | PLAN-UI-01 | — | N/A (UI-only) | unit | `cd go-backend && go test ./internal/web/... -count=1` | ✅ | ⬜ pending |
+| 17-02-02 | 02 | 1 | PLAN-UI-01 | — | N/A (UI-only) | unit | `cd go-backend && go test ./internal/web/... -count=1` | ✅ | ⬜ pending |
+
+*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
+
+---
+
+## Wave 0 Requirements
+
+Existing infrastructure covers all phase requirements. No new test files needed — `go-backend/internal/web/handlers/dashboard_components_test.go` is the model for any new component tests.
+
+---
+
+## Manual-Only Verifications
+
+| Behavior | Requirement | Why Manual | Test Instructions |
+|----------|-------------|------------|-------------------|
+| Own vs. others message visual differentiation | CHAT-UI-01 | Visual design — right/left alignment, bubble color | Load discussion page, confirm own messages appear right-aligned with brand tint, others left-aligned with white bubble |
+| Planning event chronological list | PLAN-UI-01 | Visual layout check | Load planning page, confirm overview-section layout with events listed chronologically |
+| Visual consistency with Phase 15–16 restyled surfaces | CHAT-UI-01, PLAN-UI-01 | Cross-page aesthetic check | Compare dashboard, tablo detail, discussion, and planning views side-by-side |
+
+---
+
+## Validation Sign-Off
+
+- [ ] All tasks have `` verify or Wave 0 dependencies
+- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
+- [ ] Wave 0 covers all MISSING references
+- [ ] No watch-mode flags
+- [ ] Feedback latency < 10s
+- [ ] `nyquist_compliant: true` set in frontmatter
+
+**Approval:** pending