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