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