diff --git a/.planning/STATE.md b/.planning/STATE.md
index 5fbc1ff..383e1f8 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -2,15 +2,15 @@
gsd_state_version: 1.0
milestone: v2.0
milestone_name: Collaboration, planning, and social sign-in
-status: planning
-last_updated: "2026-05-16T05:16:32.544Z"
-last_activity: 2026-05-16
+status: executing
+last_updated: "2026-05-16T05:21:16.217Z"
+last_activity: 2026-05-16 -- Phase 11 planning complete
progress:
total_phases: 5
completed_phases: 3
- total_plans: 13
+ total_plans: 15
completed_plans: 13
- percent: 100
+ percent: 87
---
# STATE
@@ -30,8 +30,8 @@ See: `.planning/PROJECT.md` (updated 2026-05-15)
Phase: 11
Plan: Not started
-Status: Ready to plan
-Last activity: 2026-05-16
+Status: Ready to execute
+Last activity: 2026-05-16 -- Phase 11 planning complete
Resume file: .planning/phases/11-individual-planning/11-UI-SPEC.md
## Phase Status
diff --git a/.planning/phases/11-individual-planning/11-01-PLAN.md b/.planning/phases/11-individual-planning/11-01-PLAN.md
new file mode 100644
index 0000000..76bdccd
--- /dev/null
+++ b/.planning/phases/11-individual-planning/11-01-PLAN.md
@@ -0,0 +1,155 @@
+---
+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"
+---
+
+
+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.
+
+
+
+@/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
+
+
+
+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.
+
+
+
+
+
+ Task 1: Add RED coverage for protected planning agenda behavior
+
+ - backend/internal/web/handlers_planning_test.go
+ - backend/internal/web/router.go
+
+
+ - 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
+
+
+ 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`.
+
+
+ cd backend && go test ./internal/web -run 'TestPlanning' -count=1
+
+
+ - `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.
+
+
+
+
+ Task 2: Implement the protected planning route, handler, view models, and agenda template
+
+ - 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
+
+
+ - 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
+
+
+ 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`.
+
+
+ cd backend && just generate && go test ./internal/web -run 'TestPlanning' -count=1
+
+
+ - `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.
+
+
+
+
+
+
+Run:
+- `cd backend && just generate`
+- `cd backend && go test ./internal/web -run 'TestPlanning' -count=1`
+- `git diff --check`
+
+
+
+- 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.
+
+
diff --git a/.planning/phases/11-individual-planning/11-02-PLAN.md b/.planning/phases/11-individual-planning/11-02-PLAN.md
new file mode 100644
index 0000000..d4fe21f
--- /dev/null
+++ b/.planning/phases/11-individual-planning/11-02-PLAN.md
@@ -0,0 +1,149 @@
+---
+phase: 11-individual-planning
+plan: 02
+type: execute
+wave: 2
+depends_on:
+ - 11-01-PLAN.md
+files_modified:
+ - backend/internal/web/handlers_planning.go
+ - backend/internal/web/handlers_planning_test.go
+ - backend/internal/web/router.go
+ - backend/templates/planning.templ
+ - backend/templates/planning_forms.go
+autonomous: false
+requirements: [PLAN-01, PLAN-02, PLAN-03, PLAN-04]
+
+must_haves:
+ truths:
+ - "All PLAN-01..04 requirements are covered by automated tests or browser UAT"
+ - "All D-01..D-10 decisions from 11-CONTEXT.md are represented by source behavior, tests, or the browser checkpoint"
+ - "UI-SPEC: the page remains a restrained agenda view inside existing Layout, with no calendar recreation and no JS framework"
+ - "Validation: full generated-code backend verification and browser UAT run before Phase 11 is marked complete"
+ artifacts:
+ - path: "backend/internal/web/handlers_planning_test.go"
+ provides: "final planning regression coverage"
+ - path: "backend/templates/planning.templ"
+ provides: "final UI contract adjustments"
+---
+
+
+Vertical slice 2: harden the individual planning page with full generated-code verification, targeted regression fixes, and the required browser UAT approval for the first upcoming-agenda behavior.
+
+
+
+@/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-VALIDATION.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/11-individual-planning/11-01-PLAN.md
+
+
+
+T-11-01 Unauthenticated access remains covered by full router tests.
+T-11-02 Cross-user event leaks remain covered by `ListUserEventsRange` tests and planning page ownership tests.
+T-11-03 XSS remains covered by templ escaped rendering and should be spot-checked with HTML-looking event/tablo/location content.
+T-11-04 Invalid date input remains covered by fallback tests.
+T-11-05 Source-link drift remains covered by response assertions for `/tablos/{id}/events?month=YYYY-MM`.
+T-11-06 Regression risk: extending `NewRouter` can silently break existing auth, tablos, tasks, etapes, events, and files test helpers if the full suite is not run.
+
+
+
+SOURCE | ID | Feature/Requirement | Plan | Status | Notes
+GOAL | - | Personal planning view aggregates scheduled events across tablos | 11-01,11-02 | COVERED | Route, query, agenda template, UAT
+REQ | PLAN-01 | Authenticated user can open individual planning page | 11-01,11-02 | COVERED | Protected route test and browser UAT
+REQ | PLAN-02 | Planning lists user's scheduled events across tablos chronologically | 11-01,11-02 | COVERED | `ListUserEventsRange` plus response-order tests
+REQ | PLAN-03 | Planning links each event back to tablo context | 11-01,11-02 | COVERED | `/tablos/{id}/events?month=YYYY-MM`
+REQ | PLAN-04 | Functional empty state and date navigation/filtering | 11-01,11-02 | COVERED | 14-day navigation and empty-state tests
+CONTEXT | D-01..D-05 | Upcoming 14-day agenda, fixed windows, no calendar recreation | 11-01,11-02 | COVERED | Handler date model and UI contract
+CONTEXT | D-06..D-10 | Continuous rows, row fields, color dot, no descriptions, no-end display | 11-01,11-02 | COVERED | Template and response assertions
+RESEARCH | Security | Protected route, authenticated user query, escaping, invalid-date fallback | 11-01,11-02 | COVERED | Threat model and tests
+UI-SPEC | Contract | Header, toolbar, agenda list, empty state, responsive/accessibility basics | 11-01,11-02 | COVERED | Template and browser UAT
+
+
+
+
+
+ Task 1: Run full generated-code backend verification and fix planning regressions
+
+ - backend/internal/web/handlers_planning.go
+ - backend/internal/web/handlers_planning_test.go
+ - backend/internal/web/router.go
+ - backend/cmd/web/main.go
+ - backend/templates/planning.templ
+ - backend/templates/planning_forms.go
+
+
+ - backend/internal/web/handlers_planning.go
+ - backend/internal/web/handlers_planning_test.go
+ - backend/internal/web/router.go
+ - backend/cmd/web/main.go
+ - backend/templates/planning.templ
+ - backend/templates/planning_forms.go
+ - .planning/phases/11-individual-planning/11-VALIDATION.md
+ - .planning/phases/11-individual-planning/11-UI-SPEC.md
+
+
+ Run generated-code verification and the full backend suite. Fix any regressions caused by `PlanningDeps`, `NewRouter` signature changes, generated templ output, route ordering, date/link helpers, or planning test fixtures.
+ Keep fixes scoped to Phase 11 files and direct router call-site updates. Do not change Phase 10 event semantics, `ListUserEventsRange`, or existing task/file/etape behavior unless a compile break from the router signature requires call-site updates.
+
+
+ cd backend && just generate && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1
+
+
+ - `cd backend && just generate` exits 0.
+ - `cd backend && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1` exits 0.
+ - `git diff --check` exits 0.
+ - Existing auth, tablos, tasks, etapes, events, and files tests still pass after adding `/planning`.
+
+
+
+
+ Task 2: Browser UAT checkpoint for individual planning
+
+
+ - .planning/phases/11-individual-planning/11-UI-SPEC.md
+ - .planning/phases/11-individual-planning/11-VALIDATION.md
+ - backend/templates/planning.templ
+
+
+ Start the app with `cd backend && just dev` or an equivalent local server using the same generated assets, sign in, create or use events across multiple tablos, and open `/planning`.
+ Verify:
+ 1. Unauthenticated `/planning` redirects to login.
+ 2. Authenticated `/planning` renders `Planning` inside the normal authenticated layout.
+ 3. The default range is today through 13 days after today.
+ 4. Events from multiple owned tablos appear in one chronological agenda list.
+ 5. Event rows show time, title, tablo title/color dot when present, and location when present.
+ 6. Event rows do not show description snippets, edit/delete controls, grouped day headers, or a calendar grid.
+ 7. Previous 14 days, Today, and Next 14 days controls work through normal links.
+ 8. Empty ranges show `No events in this range` while navigation remains visible.
+ 9. Event links return to `/tablos/{id}/events?month=YYYY-MM`.
+ 10. User approves the first upcoming-agenda behavior from the ROADMAP user-in-loop note.
+
+
+ cd backend && go test ./internal/web -run 'TestPlanning' -count=1
+ Browser confirms PLAN-01..04 and the 11-UI-SPEC interaction contract.
+
+
+ - Full backend tests pass before the browser checkpoint.
+ - Browser UAT covers auth, default range, navigation, empty state, event row content, and source tablo links.
+ - User approves today/upcoming 14-day agenda behavior before Phase 11 is marked complete.
+
+
+
+
+
+
+Run:
+- `cd backend && just generate`
+- `cd backend && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1`
+- `git diff --check`
+- Browser UAT from Task 2
+
+
+
+- All PLAN-01..04 requirements are complete.
+- All D-01..D-10 context decisions are implemented or verified.
+- UI-SPEC is satisfied by the first functional individual planning agenda.
+- Security expectations are covered by protected route mounting, owned-only aggregation, escaped rendering, and invalid-date fallback.
+
diff --git a/.planning/phases/11-individual-planning/11-PATTERNS.md b/.planning/phases/11-individual-planning/11-PATTERNS.md
new file mode 100644
index 0000000..1cc0678
--- /dev/null
+++ b/.planning/phases/11-individual-planning/11-PATTERNS.md
@@ -0,0 +1,74 @@
+---
+phase: 11
+slug: individual-planning
+status: complete
+created: 2026-05-16
+---
+
+# Phase 11 - Pattern Map
+
+## Purpose
+
+Map the individual planning implementation to existing Go/HTMX codebase patterns so execution can add a small protected agenda page without inventing parallel routing, data, template, or test conventions.
+
+## File Map
+
+| New / Modified File | Role | Closest Existing Analog | Pattern to Reuse |
+|---------------------|------|-------------------------|------------------|
+| `backend/internal/web/handlers_planning.go` | Protected planning page handler | `backend/internal/web/handlers_events.go`, `backend/internal/web/handlers_tablos.go` | Small `Deps` struct, auth user from request context, query through SQLC, log 500s, no framework state |
+| `backend/internal/web/router.go` | Route mounting | Protected route group in `router.go` | Mount `GET /planning` inside `auth.RequireAuth`; keep static routes before `/tablos/{id}` |
+| `backend/templates/planning.templ` | Agenda page template | `backend/templates/tablos.templ`, `backend/templates/events.templ` | Render inside `Layout`, use Tailwind utilities and local `ui-button` classes, semantic list output |
+| `backend/templates/planning_forms.go` | Planning view models/helpers | `backend/templates/events_forms.go` | Small structs plus URL/date helpers, reuse event date/time formatting |
+| `backend/internal/web/handlers_planning_test.go` | DB-backed planning tests | `backend/internal/web/handlers_events_test.go` | `setupTestDB`, `preInsertUser`, session cookies, full router requests, response body assertions |
+| `backend/cmd/web/main.go` | Dependency wiring | Existing `eventDeps`, `fileDeps`, `NewRouter` call | Create `planningDeps := web.PlanningDeps{Queries: q}` and pass to router |
+| Router call sites/tests | Dependency wiring | Existing `NewRouter(... eventDeps, fileDeps, ...)` updates | Add `PlanningDeps` to every `NewRouter` call with `PlanningDeps{Queries: q}` or zero value where route is not exercised |
+
+## Handler Patterns
+
+Planning should mirror existing handler dependencies:
+
+1. Define `PlanningDeps{Queries *sqlc.Queries, Now func() time.Time}`.
+2. In `PlanningPageHandler`, default `Now` to `time.Now` when nil.
+3. Read the authenticated user from context after `auth.RequireAuth`; no request parameter should identify the user.
+4. Parse `start=YYYY-MM-DD`; blank or invalid values fall back to today's local date.
+5. Compute inclusive range `start` through `start.AddDate(0, 0, 13)`.
+6. Call `ListUserEventsRange` with authenticated `user.ID`, `pgDateFromTime(start)`, and `pgDateFromTime(end)`.
+7. Render `templates.PlanningPage(user, csrf.Token(r), agenda)`.
+
+## Template Patterns
+
+Use existing server-rendered conventions:
+
+- Page shell: `@Layout("Planning - Xtablo", user, csrfToken)`.
+- Heading: `text-[28px] font-semibold leading-tight`.
+- Range label: `text-sm text-slate-600`.
+- Navigation links: `ui-button ui-button-soft-neutral-md` and `ui-button ui-button-solid-default-md` for Today when appropriate.
+- Agenda rows: one continuous `ul`/`li` list with borders, not nested cards.
+- Link URL: `/tablos/{tablo_id}/events?month=YYYY-MM` derived from the event date.
+- Color dot: inline span only when `tablo_color` is valid; title text must always render.
+
+## Test Patterns
+
+Create `backend/internal/web/handlers_planning_test.go` and reuse the existing event test style:
+
+- `setupTestDB(t)` and `defer cleanup()`.
+- `q := sqlc.New(pool)` and `store := auth.NewStore(q)`.
+- Use `preInsertUser`, `insertEventTestTablo`, `insertEventTestEvent`, and `sessionCookieForUser` patterns from `handlers_events_test.go`.
+- Route through the full `NewRouter` so auth middleware, CSRF middleware, route mounting, and layout rendering are covered.
+- Assert 404/redirect behavior using status codes already used elsewhere.
+
+## Security Patterns
+
+- Authorization belongs to `auth.RequireAuth` and `ListUserEventsRange` filtering by `tablos.user_id`.
+- Do not fetch by event ID or tablo ID from the planning route.
+- Do not hand-edit generated SQLC files.
+- User strings should render through templ expressions, not `templ.Raw`.
+- Malformed date input should degrade to the default range.
+
+## Anti-Patterns
+
+- Do not add a migration for this phase.
+- Do not duplicate the Events month calendar.
+- Do not group rows by day or by tablo.
+- Do not show description snippets, edit/delete controls, import/export UI, recurrence UI, or external calendar sync.
+- Do not introduce shadcn, Radix, lucide, custom SVG icons, or a JS framework.
diff --git a/.planning/phases/11-individual-planning/11-RESEARCH.md b/.planning/phases/11-individual-planning/11-RESEARCH.md
new file mode 100644
index 0000000..18dc8d9
--- /dev/null
+++ b/.planning/phases/11-individual-planning/11-RESEARCH.md
@@ -0,0 +1,149 @@
+---
+phase: 11
+slug: individual-planning
+status: complete
+created: 2026-05-16
+---
+
+# Phase 11 - Individual Planning Research
+
+## Research Question
+
+What needs to be known to plan a protected `/planning` agenda page that aggregates the authenticated user's events across tablos without introducing a JavaScript framework or reworking the Phase 10 event model?
+
+## Executive Summary
+
+Phase 11 can be implemented as a narrow Go/HTMX vertical slice. Phase 10 already delivered the critical query surface: `ListUserEventsRange` joins `events` to `tablos`, filters by `tablos.user_id`, accepts start/end dates, orders by date/time/title, and returns event fields plus `tablo_title` and `tablo_color`.
+
+The planning phase should not add a migration. The required work is a protected route, a small handler/view-model layer for 14-day window navigation, a server-rendered agenda template, targeted DB-backed handler tests, and final browser UAT.
+
+## Current Implementation Facts
+
+- `backend/internal/db/queries/events.sql` contains `ListUserEventsRange` with `tablos.user_id = sqlc.arg(user_id)`, inclusive `start_date`/`end_date`, and `ORDER BY events.event_date, events.start_time, events.title`.
+- Generated SQLC exposes `sqlc.ListUserEventsRangeParams` and `sqlc.ListUserEventsRangeRow`.
+- `backend/internal/web/router.go` has one protected chi group guarded by `auth.RequireAuth`; `/planning` should mount inside that group before `/tablos/{id}` parametric routes.
+- `backend/internal/web/handlers_events.go` already has reusable local-date helpers: `pgDateFromTime`, `parseEventDate`, `parseEventTime`, `parseCalendarMonth`, and event ownership patterns.
+- `backend/templates/events_forms.go` already has `FormatEventDate`, `FormatEventTime`, `FormatOptionalEventTime`, `EventTimeRange`, and `EventMonthURL`.
+- `backend/templates/layout.templ` provides the authenticated shell and `max-w-5xl` page container.
+- Existing DB-backed tests use `setupTestDB`, `preInsertUser`, `auth.NewStore`, `sessionCookieForUser`, `newEventTestRouter`, and full router requests.
+
+## Recommended Architecture
+
+Create a dedicated planning handler and template pair:
+
+- `backend/internal/web/handlers_planning.go`
+ - Add `PlanningDeps{Queries *sqlc.Queries}`.
+ - Add `PlanningPageHandler(deps PlanningDeps) http.HandlerFunc`.
+ - Parse optional `start=YYYY-MM-DD`; invalid or blank defaults to today's local date.
+ - Compute inclusive 14-day window: `start` through `start.AddDate(0, 0, 13)`.
+ - Query `deps.Queries.ListUserEventsRange` using `auth.UserFromContext(r.Context()).ID`.
+ - Return 500 and log if the query fails.
+
+- `backend/templates/planning_forms.go`
+ - Add `PlanningAgenda` and `PlanningEventRow` structs.
+ - Add URL/date helpers: `PlanningURL(start time.Time)`, `PlanningEventURL(row sqlc.ListUserEventsRangeRow)`, and `PlanningRangeLabel(start, end time.Time)`.
+ - Reuse `FormatEventDate`, `FormatEventTime`, and `EventTimeRange` conventions instead of introducing parallel formatting.
+
+- `backend/templates/planning.templ`
+ - Add `PlanningPage(user *auth.User, csrfToken string, agenda PlanningAgenda)`.
+ - Render inside `Layout("Planning - Xtablo", user, csrfToken)`.
+ - Render a single `h1` with `Planning`, a compact range label, previous/today/next controls, and one continuous chronological list.
+ - Each row links to `/tablos/{tablo_id}/events?month=YYYY-MM`.
+ - Render location only when present and never render description snippets.
+
+## Planning Window Semantics
+
+Use fixed 14-day windows:
+
+- Default: today through 13 days after today.
+- `Previous 14 days`: `start.AddDate(0, 0, -14)`.
+- `Next 14 days`: `start.AddDate(0, 0, 14)`.
+- `Today`: `/planning` or `/planning?start=YYYY-MM-DD` for today's date.
+- Query parameter recommendation: `start=YYYY-MM-DD`.
+
+For deterministic tests, inject time into the handler dependency:
+
+```go
+type PlanningDeps struct {
+ Queries *sqlc.Queries
+ Now func() time.Time
+}
+```
+
+When `Now` is nil, default to `time.Now`.
+
+## Security and Authorization
+
+Threats the plans must address:
+
+- **T-11-01 unauthenticated access:** `/planning` must be inside the `auth.RequireAuth` group, preserving plain 303 redirect and HTMX auth behavior.
+- **T-11-02 cross-user event leak:** all event aggregation must use `ListUserEventsRange` with the authenticated user's ID; no client-supplied user or tablo ID may drive the query.
+- **T-11-03 XSS:** event titles, tablo titles, and locations are user-controlled and must render through templ escaped expressions.
+- **T-11-04 invalid date input:** malformed `start` must not produce a 500 or unsafe query; default to today.
+- **T-11-05 URL/link confusion:** event links must point back to the source tablo Events tab with a `month=YYYY-MM` derived from the event date, not from the current planning window.
+
+## UI Findings
+
+The approved `11-UI-SPEC.md` is specific and should be treated as binding:
+
+- The page is an agenda, not a calendar.
+- Use the existing authenticated `Layout` and local `ui.Button` classes.
+- No shadcn, Radix, lucide, SVG icon work, or JS framework.
+- Agenda rows must show time, title, tablo context, and location when present.
+- Rows must not show descriptions, create/edit/delete controls, day grouping, or nested card layouts.
+- The tablo color dot is decorative; always show the tablo title text.
+
+## Existing Pattern Map
+
+Closest analogs:
+
+- Router mounting: `backend/internal/web/router.go` protected group.
+- Handler dependencies: `EventsDeps`, `TasksDeps`, `FilesDeps`.
+- Date parsing and range queries: `handlers_events.go`.
+- Template helpers: `events_forms.go`.
+- Page shell: `TablosDashboard` and `TabloDetailPage`.
+- DB-backed handler tests: `handlers_events_test.go`, `handlers_tablos_test.go`.
+
+## Validation Architecture
+
+The phase has strong automated coverage opportunities. No schema push is required because no migration or SQL query file change is needed.
+
+Recommended test layers:
+
+1. DB-backed web tests for route protection and aggregation behavior.
+2. Template/handler response assertions for copy, links, chronological order, empty state, and no description snippets.
+3. Full generated-code verification with `just generate`.
+4. Browser UAT for the first working planning view.
+
+Critical tests:
+
+- `TestPlanningRequiresAuth`: unauthenticated `GET /planning` redirects to `/login`.
+- `TestPlanningDefaultsToUpcomingFourteenDays`: with injected `Now=2026-05-16`, events from `2026-05-16` through `2026-05-29` render and `2026-05-30` does not.
+- `TestPlanningListsOwnedEventsChronologically`: owned events across two tablos render in date/time/title order with titles, times, tablo titles, color dot marker, locations, and `/tablos/{id}/events?month=YYYY-MM` links.
+- `TestPlanningDoesNotLeakOtherUsersEvents`: foreign user's event in the same date window is absent.
+- `TestPlanningNavigationLinks`: page contains `Previous 14 days`, `Today`, and `Next 14 days` links with expected `start` values.
+- `TestPlanningEmptyState`: empty range renders `No events in this range` and keeps navigation controls visible.
+- `TestPlanningInvalidStartFallsBackToToday`: `GET /planning?start=not-a-date` renders the injected today range, not a 500.
+
+Run commands:
+
+- Targeted: `cd backend && go test ./internal/web -run 'TestPlanning' -count=1`
+- Full backend: `cd backend && just generate && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1`
+- Whitespace: `git diff --check`
+
+## Planning Recommendation
+
+Use two plans:
+
+1. A vertical planning page slice that adds route, handler, view model, template, navigation, aggregation, and automated tests.
+2. A hardening and UAT plan that runs full generated-code verification, fixes regressions, and records the user-in-loop approval for upcoming agenda behavior.
+
+This keeps Phase 11 small while still covering PLAN-01 through PLAN-04 and all context decisions D-01 through D-10.
+
+## Open Questions
+
+None blocking. The user-in-loop question from ROADMAP asks whether the first working behavior should be today/upcoming filtering versus week navigation; CONTEXT decisions D-01 through D-05 already lock the answer to an upcoming 14-day agenda with fixed-window navigation.
+
+## Research Complete
+
+Phase 11 is ready for planning.
diff --git a/.planning/phases/11-individual-planning/11-VALIDATION.md b/.planning/phases/11-individual-planning/11-VALIDATION.md
new file mode 100644
index 0000000..af3b2b3
--- /dev/null
+++ b/.planning/phases/11-individual-planning/11-VALIDATION.md
@@ -0,0 +1,78 @@
+---
+phase: 11
+slug: individual-planning
+status: draft
+nyquist_compliant: true
+wave_0_complete: true
+created: 2026-05-16
+---
+
+# Phase 11 - Validation Strategy
+
+> Per-phase validation contract for feedback sampling during execution.
+
+---
+
+## Test Infrastructure
+
+| Property | Value |
+|----------|-------|
+| **Framework** | Go `testing` with DB-backed integration helpers |
+| **Config file** | `backend/go.mod`, `backend/sqlc.yaml`, `backend/justfile` |
+| **Quick run command** | `cd backend && go test ./internal/web -run 'TestPlanning' -count=1` |
+| **Full suite command** | `cd backend && just generate && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1` |
+| **Estimated runtime** | ~60 seconds with local Postgres already running |
+
+---
+
+## Sampling Rate
+
+- **After every task commit:** Run `cd backend && go test ./internal/web -run 'TestPlanning' -count=1` when planning tests exist; otherwise run the named targeted test from the task.
+- **After every plan wave:** Run `cd backend && just generate && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1`.
+- **Before `$gsd-verify-work`:** Full suite must be green and `git diff --check` must pass.
+- **Max feedback latency:** 90 seconds with local services available.
+
+---
+
+## Per-Task Verification Map
+
+| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
+|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
+| 11-01-01 | 01 | 1 | PLAN-01, PLAN-02, PLAN-03, PLAN-04 | T-11-01, T-11-02, T-11-04, T-11-05 | Protected route, owned-only aggregation, valid fallback date, source tablo links | DB/integration | `cd backend && go test ./internal/web -run 'TestPlanning' -count=1` | W0 | pending |
+| 11-01-02 | 01 | 1 | PLAN-01, PLAN-02, PLAN-03, PLAN-04 | T-11-02, T-11-03, T-11-05 | Server-rendered agenda escapes content and links back to owned tablo context | DB/integration | `cd backend && just generate && go test ./internal/web -run 'TestPlanning' -count=1` | W0 | pending |
+| 11-02-01 | 02 | 2 | PLAN-01, PLAN-02, PLAN-03, PLAN-04 | T-11-01..T-11-05 | Full generated backend remains green after route/template additions | Full suite | `cd backend && just generate && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1` | W0 | pending |
+| 11-02-02 | 02 | 2 | PLAN-01, PLAN-02, PLAN-03, PLAN-04 | T-11-01..T-11-05 | Browser UAT confirms protected agenda behavior and navigation | Manual UAT | `cd backend && go test ./internal/web -run 'TestPlanning' -count=1` | W0 | pending |
+
+*Status: pending / green / red / flaky*
+
+---
+
+## Wave 0 Requirements
+
+Existing infrastructure covers all phase requirements:
+
+- `backend/internal/web/testdb_test.go` provides schema-isolated DB setup.
+- `backend/internal/web/handlers_auth_test.go` provides `preInsertUser`.
+- `backend/internal/web/handlers_events_test.go` provides reusable event/tablo/session helper patterns.
+- `backend/justfile` provides `just generate`.
+
+---
+
+## Manual-Only Verifications
+
+| Behavior | Requirement | Why Manual | Test Instructions |
+|----------|-------------|------------|-------------------|
+| First working agenda UX approval | PLAN-01, PLAN-04 | ROADMAP user-in-loop requires approval of today/upcoming behavior vs week navigation | Start app, sign in, open `/planning`, confirm default 14-day upcoming agenda, previous/today/next controls, empty state, and event deep links. |
+
+---
+
+## Validation Sign-Off
+
+- [x] All tasks have `` verify or Wave 0 dependencies
+- [x] Sampling continuity: no 3 consecutive tasks without automated verify
+- [x] Wave 0 covers all missing references
+- [x] No watch-mode flags
+- [x] Feedback latency < 90s
+- [x] `nyquist_compliant: true` set in frontmatter
+
+**Approval:** approved 2026-05-16