---
phase: 11-individual-planning
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - backend/internal/web/handlers_planning_test.go
  - backend/internal/web/handlers_planning.go
  - backend/internal/web/router.go
  - backend/cmd/web/main.go
  - backend/templates/planning.templ
  - backend/templates/planning_forms.go
autonomous: true
requirements: [PLAN-01, PLAN-02, PLAN-03, PLAN-04]

must_haves:
  truths:
    - "D-01/D-02: /planning defaults to an upcoming 14-day agenda starting today"
    - "D-03/D-04: navigation uses fixed 14-day windows with Today, previous-window, and next-window controls; past events are reachable through previous-window navigation"
    - "D-05/D-06: planning is a single continuous agenda list, not a month/week calendar and not grouped by day or tablo"
    - "D-07/D-08/D-10: each row shows time, title, tablo context, and location when present; no description snippets; no-end events display start time only"
    - "D-09: tablo context shows title plus a color dot when tablo_color is present"
    - "UI-SPEC: page uses existing Layout, compact header, toolbar, semantic agenda list, empty state, and no JS framework"
    - "PLAN-03: every event links back to /tablos/{id}/events?month=YYYY-MM for its event date"
    - "Security: planning aggregation uses authenticated user ID with ListUserEventsRange; no client-supplied user/tablo ID drives results"
  artifacts:
    - path: "backend/internal/web/handlers_planning.go"
      provides: "protected /planning route handler and 14-day range query"
    - path: "backend/templates/planning.templ"
      provides: "server-rendered planning agenda page"
    - path: "backend/templates/planning_forms.go"
      provides: "planning view models and date/link helpers"
    - path: "backend/internal/web/handlers_planning_test.go"
      provides: "DB-backed planning behavior and authorization coverage"
---

<objective>
Vertical slice 1: add the protected `/planning` page that renders the authenticated user's accessible events across tablos as a fixed 14-day chronological agenda with working navigation, empty state, and source tablo links.
</objective>

<context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/11-individual-planning/11-CONTEXT.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/11-individual-planning/11-RESEARCH.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/11-individual-planning/11-UI-SPEC.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/11-individual-planning/11-PATTERNS.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-03-SUMMARY.md
</context>

<threat_model>
T-11-01 Unauthenticated access: `/planning` must be mounted inside the protected `auth.RequireAuth` route group so unauthenticated users are redirected to `/login`.
T-11-02 Cross-user event leak: handler must call `ListUserEventsRange` with `auth.UserFromContext(r.Context()).ID`; no query parameter may select user or tablo scope.
T-11-03 XSS: event titles, tablo titles, locations, and color strings must render through templ escaped expressions; do not use `templ.Raw`.
T-11-04 Invalid date input: malformed `start` query values must fall back to today's local date instead of returning 500 or generating unsafe SQL.
T-11-05 Source-link drift: event deep links must derive `month=YYYY-MM` from each event's own `event_date`, not from the current planning window.
</threat_model>

<tasks>

<task type="auto">
  <name>Task 1: Add RED coverage for protected planning agenda behavior</name>
  <files>
    - backend/internal/web/handlers_planning_test.go
    - backend/internal/web/router.go
  </files>
  <read_first>
    - backend/internal/web/handlers_events_test.go
    - backend/internal/web/handlers_tablos_test.go
    - backend/internal/web/router.go
    - backend/internal/db/queries/events.sql
    - .planning/phases/11-individual-planning/11-CONTEXT.md
    - .planning/phases/11-individual-planning/11-UI-SPEC.md
    - .planning/phases/11-individual-planning/11-RESEARCH.md
  </read_first>
  <action>
    Create `backend/internal/web/handlers_planning_test.go` with DB-backed tests that initially fail:
    1. `TestPlanningRequiresAuth` sends unauthenticated `GET /planning` and expects a redirect to `/login`.
    2. `TestPlanningDefaultsToUpcomingFourteenDays` injects `Now` as `2026-05-16`, creates owned events on `2026-05-16`, `2026-05-29`, and `2026-05-30`, then expects the first two titles and not the third.
    3. `TestPlanningListsOwnedEventsChronologically` creates owned events across two tablos in non-chronological insert order and expects response order by date, start time, then title; it also expects visible event title, time, tablo title, color value or dot marker, location, and `/tablos/{id}/events?month=YYYY-MM`.
    4. `TestPlanningDoesNotLeakOtherUsersEvents` creates an event for another user in the same range and expects the foreign title to be absent.
    5. `TestPlanningNavigationLinks` expects visible `Previous 14 days`, `Today`, and `Next 14 days` links with `start=2026-05-02`, `/planning` or `start=2026-05-16`, and `start=2026-05-30`.
    6. `TestPlanningEmptyState` expects `No events in this range` and `Use the navigation controls to browse another 14-day window.` while the navigation controls remain visible.
    7. `TestPlanningInvalidStartFallsBackToToday` requests `/planning?start=not-a-date` with injected `Now=2026-05-16` and expects the same default range label as `/planning`.
    Add a local `newPlanningTestRouter(q, store, now)` helper that wires `PlanningDeps{Queries: q, Now: now}` through `NewRouter`.
  </action>
  <verify>
    <automated>cd backend && go test ./internal/web -run 'TestPlanning' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `backend/internal/web/handlers_planning_test.go` contains all seven named `TestPlanning...` tests.
    - Tests use full router requests with authenticated session cookies where needed.
    - The ownership test proves another user's event title is not present in `/planning`.
    - The default-window test proves `2026-05-16` and `2026-05-29` are included while `2026-05-30` is excluded.
  </acceptance_criteria>
</task>

<task type="auto">
  <name>Task 2: Implement the protected planning route, handler, view models, and agenda template</name>
  <files>
    - backend/internal/web/handlers_planning.go
    - backend/internal/web/router.go
    - backend/cmd/web/main.go
    - backend/templates/planning.templ
    - backend/templates/planning_forms.go
  </files>
  <read_first>
    - backend/internal/web/handlers_events.go
    - backend/internal/web/router.go
    - backend/cmd/web/main.go
    - backend/templates/layout.templ
    - backend/templates/events_forms.go
    - backend/templates/events.templ
    - backend/templates/tablos.templ
    - backend/internal/db/queries/events.sql
    - .planning/phases/11-individual-planning/11-UI-SPEC.md
    - .planning/phases/11-individual-planning/11-PATTERNS.md
  </read_first>
  <action>
    Add `PlanningDeps{Queries *sqlc.Queries, Now func() time.Time}` and `PlanningPageHandler(deps PlanningDeps)` in `handlers_planning.go`.
    The handler must default `Now` to `time.Now`, parse `start` with layout `2006-01-02`, fall back to today's local date when blank or invalid, compute `end := start.AddDate(0, 0, 13)`, and call `ListUserEventsRange` with authenticated `user.ID`, `pgDateFromTime(start)`, and `pgDateFromTime(end)`.
    Extend `NewRouter` to accept `planningDeps PlanningDeps` after `eventDeps EventsDeps` and before `fileDeps FilesDeps`; mount `r.Get("/planning", PlanningPageHandler(planningDeps))` inside the protected route group before `/tablos/new` and `/tablos/{id}` routes.
    Update `backend/cmd/web/main.go` to construct `planningDeps := web.PlanningDeps{Queries: q}` and pass it to `NewRouter`; update all `NewRouter` test helper call sites with `PlanningDeps{Queries: q}` or `PlanningDeps{}` where the route is not exercised.
    Add `PlanningAgenda` and `PlanningEventRow` helpers in `planning_forms.go` with exact URL helper behavior: planning navigation URLs use `start=YYYY-MM-DD`, today can use `/planning`, and event links use `/tablos/{tablo_id}/events?month=YYYY-MM` derived from each event date.
    Add `PlanningPage` in `planning.templ` using existing `Layout`, `ui-button` classes, one `h1` with `Planning`, a concise date range label, previous/today/next links, a semantic continuous agenda list, compact rows, optional location, decorative tablo color dot, and the exact empty-state copy from `11-UI-SPEC.md`.
    Do not add migrations, JavaScript framework code, calendar grids, description snippets, grouping headings, create/edit/delete controls, registry components, or custom SVG icons.
    Run `cd backend && just generate`.
  </action>
  <verify>
    <automated>cd backend && just generate && go test ./internal/web -run 'TestPlanning' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `backend/internal/web/router.go` contains `r.Get("/planning", PlanningPageHandler(planningDeps))` inside the protected group.
    - `PlanningPageHandler` calls `ListUserEventsRange` with the authenticated user's ID.
    - `planning.templ` contains `Planning`, `Previous 14 days`, `Today`, `Next 14 days`, `No events in this range`, and no description rendering.
    - Event links contain `/tablos/` and `/events?month=`.
    - `cd backend && just generate && go test ./internal/web -run 'TestPlanning' -count=1` exits 0.
  </acceptance_criteria>
</task>

</tasks>

<verification>
Run:
- `cd backend && just generate`
- `cd backend && go test ./internal/web -run 'TestPlanning' -count=1`
- `git diff --check`
</verification>

<success_criteria>
- PLAN-01: authenticated users can open `/planning`; unauthenticated users are redirected.
- PLAN-02: planning lists owned accessible events across tablos in chronological order.
- PLAN-03: each listed event links back to its tablo Events tab with the event month.
- PLAN-04: empty state and fixed 14-day navigation work without a JS framework.
</success_criteria>

