docs(11): create phase plan
This commit is contained in:
parent
aa897b1fab
commit
29b1e904c0
6 changed files with 612 additions and 7 deletions
|
|
@ -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
|
||||
|
|
|
|||
155
.planning/phases/11-individual-planning/11-01-PLAN.md
Normal file
155
.planning/phases/11-individual-planning/11-01-PLAN.md
Normal 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>
|
||||
|
||||
149
.planning/phases/11-individual-planning/11-02-PLAN.md
Normal file
149
.planning/phases/11-individual-planning/11-02-PLAN.md
Normal 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>
|
||||
74
.planning/phases/11-individual-planning/11-PATTERNS.md
Normal file
74
.planning/phases/11-individual-planning/11-PATTERNS.md
Normal 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.
|
||||
149
.planning/phases/11-individual-planning/11-RESEARCH.md
Normal file
149
.planning/phases/11-individual-planning/11-RESEARCH.md
Normal 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.
|
||||
78
.planning/phases/11-individual-planning/11-VALIDATION.md
Normal file
78
.planning/phases/11-individual-planning/11-VALIDATION.md
Normal 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
|
||||
Loading…
Reference in a new issue