go-htmx-gsd #1

Merged
arthur merged 558 commits from go-htmx-gsd into main 2026-05-23 15:16:44 +00:00
6 changed files with 612 additions and 7 deletions
Showing only changes of commit 29b1e904c0 - Show all commits

View file

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

View file

@ -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"
---
<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>

View file

@ -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"
---
<objective>
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.
</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-VALIDATION.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/11-individual-planning/11-01-PLAN.md
</context>
<threat_model>
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.
</threat_model>
<source_audit>
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
</source_audit>
<tasks>
<task type="auto">
<name>Task 1: Run full generated-code backend verification and fix planning regressions</name>
<files>
- 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
</files>
<read_first>
- 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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && just generate && TEST_DATABASE_URL='postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable' go test ./... -count=1</automated>
</verify>
<acceptance_criteria>
- `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`.
</acceptance_criteria>
</task>
<task type="checkpoint:human-verify">
<name>Task 2: Browser UAT checkpoint for individual planning</name>
<files></files>
<read_first>
- .planning/phases/11-individual-planning/11-UI-SPEC.md
- .planning/phases/11-individual-planning/11-VALIDATION.md
- backend/templates/planning.templ
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && go test ./internal/web -run 'TestPlanning' -count=1</automated>
<manual>Browser confirms PLAN-01..04 and the 11-UI-SPEC interaction contract.</manual>
</verify>
<acceptance_criteria>
- 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.
</acceptance_criteria>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- 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.
</success_criteria>

View file

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

View file

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

View file

@ -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 `<automated>` 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