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