---
phase: 17-chat-planning
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - go-backend/internal/web/ui/app.css
  - go-backend/internal/web/views/planning_view.go
  - go-backend/internal/web/views/planning_view_test.go
  - go-backend/internal/web/views/dashboard_components.templ
  - go-backend/internal/web/handlers/auth.go
autonomous: false
requirements:
  - PLAN-UI-01

must_haves:
  truths:
    - "A user visiting /planning sees a heading wrapped in .overview-section / .overview-section-heading with an h1 styled to match the dashboard section headings"
    - "Events in the planning list are grouped under date-separator headers — the date label appears once per day group, not in every event row"
    - "go test ./... -count=1 exits 0 after all changes"
  artifacts:
    - path: "go-backend/internal/web/views/planning_view.go"
      provides: "PlanningEventRow, PlanningTabData, NewPlanningTabData, PlanningShowDaySeparator"
      exports: ["PlanningEventRow", "PlanningTabData", "NewPlanningTabData", "PlanningShowDaySeparator"]
    - path: "go-backend/internal/web/views/planning_view_test.go"
      provides: "logic test for PlanningShowDaySeparator + render assertions for .overview-section, .overview-section-heading, separator div"
      contains: "TestPlanning"
    - path: "go-backend/internal/web/ui/app.css"
      provides: "h1 added to .overview-section-heading heading selector"
      contains: "overview-section-heading h1"
  key_links:
    - from: "go-backend/internal/web/handlers/auth.go"
      to: "views.PlanningMainContent(data)"
      via: "GetPlanningPage passes views.NewPlanningTabData()"
      pattern: "NewPlanningTabData"
    - from: "go-backend/internal/web/views/dashboard_components.templ"
      to: "PlanningTabData"
      via: "PlanningMainContent(data PlanningTabData)"
      pattern: "PlanningMainContent"
    - from: "dashboard_components.templ PlanningMainContent"
      to: "PlanningShowDaySeparator"
      via: "for i, event := range data.Events conditional"
      pattern: "PlanningShowDaySeparator"
---

## Phase Goal

**As a** signed-in user, **I want to** see the planning page styled with the overview-section layout and date-grouped event separators, **so that** the planning view is visually consistent with the Phase 15-16 restyled tablo surfaces.

<objective>
Replace the PlanningMainContent() placeholder stub with a real planning view: overview-section heading, day-separated event list using PlanningShowDaySeparator, and empty-state via @ui.EmptyState.

Purpose: PLAN-UI-01 requires the planning page to use the overview-section layout for event aggregation. This is a restyling-only slice — no new routes, no schema changes, no real data layer.
Output: planning_view.go (view model + day separator helper), planning_view_test.go (logic + render tests), h1 selector extension in app.css, PlanningMainContent() replacement in dashboard_components.templ, handler call-site update in auth.go.
</objective>

<execution_context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/ROADMAP.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/17-chat-planning/17-SPEC.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/17-chat-planning/17-CONTEXT.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/17-chat-planning/17-RESEARCH.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/17-chat-planning/17-PATTERNS.md

<interfaces>
<!-- Key contracts the executor needs. Extracted from codebase. -->

From go-backend/internal/web/views/dashboard_components.templ (lines 162-165):
Current stubs being replaced:
  templ PlanningMainContent() { @AppSectionMainContent("Planning", "Visualisez le rythme...") }

From go-backend/internal/web/handlers/auth.go (lines 123-127):
  func (h *AuthHandler) GetPlanningPage() http.HandlerFunc {
      return h.renderAppPage("/planning", func(user PublicUser) templ.Component {
          return views.PlanningMainContent()   // <-- becomes views.PlanningMainContent(views.NewPlanningTabData())
      })
  }

From go-backend/internal/web/views/dashboard_components.templ (lines 238-257) — OverviewProjectsSection pattern to follow:
  <section id="overview-projects-section" class="overview-section">
      <div class="overview-section-heading">
          <h3>Mes Projets</h3>
      </div>
      ...
  </section>

Planning variant uses <h1>Planning</h1> instead of h3 — requires app.css selector extension (see Task 1).

From go-backend/internal/web/ui/app.css (lines 886-892) — selector to extend:
  .overview-section-heading h3,
  .tasks-section-header h3 {
    color: var(--color-surface-muted-inverse);
    font-size: 1.6rem;
    font-weight: 600;
    margin: 0;
  }
  Change to add h1: ".overview-section-heading h1," as first line of the selector list.

From go-backend/internal/web/views/home.go — demo data factory pattern:
  type dashboardTask struct { Title, Project, ... string; Completed bool }
  func overviewTasks() []dashboardTask { return []dashboardTask{...} }

From go-backend/internal/web/views/dashboard_components_test.go (lines 143-151):
  // Already defined — do NOT redefine in new test files:
  func renderViewToString(t *testing.T, component templ.Component) string { ... }

From go-backend/internal/web/views/tablos.templ (EmptyState usage pattern):
  @ui.EmptyState(ui.EmptyStateProps{
      Title:       "Aucun projet trouvé",
      Description: "Créez votre premier projet",
      Icon:        ui.UIIcon("grid3x3"),
  })
  For planning: no Action field needed — omit it.
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: CSS h1 selector fix + view model + render and logic tests (RED → write)</name>
  <files>go-backend/internal/web/ui/app.css, go-backend/internal/web/views/planning_view.go, go-backend/internal/web/views/planning_view_test.go</files>

  <read_first>
    - go-backend/internal/web/ui/app.css — lines 884-895 (the .overview-section-heading h3 selector that needs h1 added; confirm exact line numbers before editing)
    - go-backend/internal/web/views/home.go — full file for demo data factory pattern (dashboardTask struct, overviewTasks function)
    - go-backend/internal/web/views/dashboard_components_test.go — lines 143-151 (renderViewToString, do NOT redeclare)
    - go-backend/internal/web/views/tablos.templ — lines 95-112 (EmptyState call with ui.EmptyStateProps — confirm exact field names Icon, Title, Description, Action)
  </read_first>

  <behavior>
    - Logic test: PlanningShowDaySeparator(events, 0) returns true regardless of date
    - Logic test: PlanningShowDaySeparator(events, 1) returns false when events[1].DateLabel == events[0].DateLabel
    - Logic test: PlanningShowDaySeparator(events, 2) returns true when events[2].DateLabel != events[1].DateLabel
    - Render test: renderViewToString(PlanningMainContent(data)) with 3 events spanning 2 dates → HTML contains "overview-section"
    - Render test: same render → HTML contains "overview-section-heading"
    - Render test: same render → HTML contains attribute data-day-separator (day separator element — see action note on separator markup)
    - Note: These tests will fail (RED) until PlanningMainContent is replaced in Task 2
  </behavior>

  <action>
  Step 1 — Edit go-backend/internal/web/ui/app.css:
  Find the selector block at approximately line 886 that reads:
    .overview-section-heading h3,
    .tasks-section-header h3 {
  Prepend ".overview-section-heading h1," as a new first line of the selector list so it becomes:
    .overview-section-heading h1,
    .overview-section-heading h3,
    .tasks-section-header h3 {
  The body of the rule (color, font-size 1.6rem, font-weight 600, margin 0) stays unchanged.
  No other changes to app.css in this task — the .message-* block is handled by Plan 01 Task 1.

  Step 2 — Create go-backend/internal/web/views/planning_view.go with package views:
  Define PlanningEventRow struct with fields: DateLabel string, TimeRange string, Title string, TabloTitle string, Location string.
  Define PlanningTabData struct with fields: Events []PlanningEventRow, DateRange string (e.g. "May 17 – May 30, 2026").
  Define NewPlanningTabData() PlanningTabData that returns a PlanningTabData with DateRange set to a static demo range and 5 hardcoded demo events spanning 2 distinct DateLabel values:
    - 3 events with DateLabel "May 17, 2026" — different TimeRange/Title/TabloTitle values
    - 2 events with DateLabel "May 18, 2026" — different TimeRange/Title/TabloTitle values
  This ensures PlanningShowDaySeparator will render 2 separator headers in the demo view.
  Define PlanningShowDaySeparator(events []PlanningEventRow, index int) bool:
    if index == 0 { return true }
    return events[index].DateLabel != events[index-1].DateLabel
  No imports needed — all fields are plain string and bool.

  Note on file location (D-P02 path adaptation): D-P02 in CONTEXT.md names `planning_forms.go` as the target for PlanningShowDaySeparator. That file does not exist in go-backend/. The go-backend/ equivalent is `planning_view.go` — this is a path adaptation, not a decision override. PlanningShowDaySeparator lives in planning_view.go as specified above.

  Step 3 — Create go-backend/internal/web/views/planning_view_test.go with package views:
  Import bytes, context, strings, testing. Do NOT redeclare renderViewToString.
  Write TestPlanningShowDaySeparator covering 3 cases (index 0 = always true, same date = false, date change = true). Use a table-driven test with t.Run subtests.
  Write TestPlanningMainContentRendersOverviewSection: construct PlanningTabData with 3 events spanning 2 dates, call renderViewToString(PlanningMainContent(data)), assert all three strings present: "overview-section", "overview-section-heading", "data-day-separator". Use t.Fatalf with the missing-string pattern (see PATTERNS.md render assertion template).
  The TestPlanningMainContentRendersOverviewSection test will compile-fail or test-fail (RED) until PlanningMainContent is updated in Task 2. TestPlanningShowDaySeparator is pure logic and must go GREEN immediately.
  </action>

  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/go-backend && go test ./internal/web/views/ -run TestPlanningShowDaySeparator -count=1 2>&1</automated>
  </verify>

  <acceptance_criteria>
    - go-backend/internal/web/ui/app.css line ~886 contains ".overview-section-heading h1," immediately before ".overview-section-heading h3,"
    - go-backend/internal/web/views/planning_view.go exists, package views, exports PlanningEventRow, PlanningTabData, NewPlanningTabData, PlanningShowDaySeparator
    - NewPlanningTabData() returns a PlanningTabData where len(Events) == 5 and Events contain exactly 2 distinct DateLabel values
    - PlanningShowDaySeparator: grep for "func PlanningShowDaySeparator" in planning_view.go returns 1 match
    - planning_view_test.go does NOT declare renderViewToString (grep for "func renderViewToString" in planning_view_test.go returns 0 matches)
    - `go test ./internal/web/views/ -run TestPlanningShowDaySeparator -count=1` exits 0 with output "PASS" — logic tests are GREEN immediately
    - `go test ./internal/web/views/ -run TestPlanningMainContentRendersOverviewSection -count=1` exits non-zero (RED — PlanningMainContent still has old stub signature)
    - The day separator div in the templ component includes the attribute data-day-separator="true" so that the render test can assert its presence without coupling to a Tailwind class name
  </acceptance_criteria>

  <done>CSS selector extended; view model + helper created; logic tests GREEN; render test written and RED (expected)</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: PlanningMainContent() templ component + handler wiring (GREEN)</name>
  <files>go-backend/internal/web/views/dashboard_components.templ, go-backend/internal/web/handlers/auth.go</files>

  <read_first>
    - go-backend/internal/web/views/dashboard_components.templ — lines 162-168 (PlanningMainContent and ChatMainContent stubs) and lines 238-260 (OverviewProjectsSection — exact heading/section HTML pattern to follow)
    - go-backend/internal/web/handlers/auth.go — lines 123-134 (GetPlanningPage current call site)
    - go-backend/internal/web/views/planning_view.go — just created in Task 1 (confirm PlanningTabData fields and NewPlanningTabData signature)
    - go-backend/internal/web/views/tablos.templ — lines 95-112 (exact @ui.EmptyState call site with ui.EmptyStateProps field names)
  </read_first>

  <behavior>
    - After changes: TestPlanningMainContentRendersOverviewSection passes (GREEN)
    - TestPlanningShowDaySeparator remains GREEN (no regression)
    - `go build ./...` exits 0
    - `go test ./... -count=1` exits 0
  </behavior>

  <action>
  Step 1 — Replace PlanningMainContent() stub in dashboard_components.templ:
  Change the signature from `templ PlanningMainContent()` to `templ PlanningMainContent(data PlanningTabData)`.
  The body renders:

  Outer wrapper: <div class="p-6"> (matches the padding used by other app sections)

  Heading section (per D-P01):
    <section class="overview-section">
      <div class="overview-section-heading">
        <div> containing <h1>Planning</h1>
        Right side div: date range label as <span class="text-sm text-slate-500">{ data.DateRange }</span> + any nav button placeholders (can be empty divs for now — the planning page navigation is pre-existing; this plan does not add new controls)
      </div>
    </section>

  Event list section below the heading:
  If len(data.Events) == 0:
    @ui.EmptyState(ui.EmptyStateProps{
        Title:       "No events in this range",
        Description: "Use the navigation controls to browse another 14-day window.",
        Icon:        ui.UIIcon("calendar"),
    })
  If events exist: render a <ul> or <div> with the day-separated event list using a for loop:
    for i, event := range data.Events {
        if PlanningShowDaySeparator(data.Events, i) {
            <div data-day-separator="true" class="px-4 py-2 text-center text-sm text-slate-500">
                { event.DateLabel }
            </div>
        }
        Render an event row: a <div class="flex items-start gap-4 py-3 border-b border-slate-100"> or similar simple row containing:
          - TimeRange: <span class="text-sm text-slate-500 w-20 shrink-0">{ event.TimeRange }</span>
          - Title: <span class="font-medium text-slate-800 grow">{ event.Title }</span>
          - TabloTitle + Location right column: <span class="text-sm text-slate-500">{ event.TabloTitle } if event.Location != "" then " · " + event.Location </span>
        DateLabel is NOT rendered inside the event row — it appears only in the day separator (per D-P04).
    }

  Important: Add data-day-separator="true" to the separator div (as shown above). This makes the render test assertion stable — the test asserts "data-day-separator" rather than a specific Tailwind class name. The visual appearance (background, text style) is controlled via Tailwind utility classes on the same element.

  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).
  </action>

  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/go-backend && go test ./... -count=1 2>&1</automated>
  </verify>

  <acceptance_criteria>
    - 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 "data-day-separator" (day separator element present — proven by TestPlanningMainContentRendersOverviewSection)
    - templ generate produced an updated dashboard_components_templ.go
  </acceptance_criteria>

  <done>PlanningMainContent wired with real view, tests GREEN, full suite passes</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 3: Browser verify — planning view visual result + combined walkthrough</name>
  <what-built>
    - .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)
  </what-built>
  <how-to-verify>
    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
    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
  </how-to-verify>
  <resume-signal>Type "approved" to continue, or describe any visual issues to fix</resume-signal>
</task>

</tasks>

<threat_model>
## 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. |
</threat_model>

<verification>
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.
</verification>

<success_criteria>
- ".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 (with data-day-separator="true") + 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
</success_criteria>

<output>
After completion, create `.planning/phases/17-chat-planning/17-02-SUMMARY.md`
</output>
