docs(10): research events phase
This commit is contained in:
parent
3aeace89fb
commit
9bab2801d8
1 changed files with 207 additions and 0 deletions
207
.planning/phases/10-events/10-RESEARCH.md
Normal file
207
.planning/phases/10-events/10-RESEARCH.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
phase: 10
|
||||
slug: events
|
||||
status: complete
|
||||
researched: 2026-05-16
|
||||
requirements:
|
||||
- EVENT-01
|
||||
- EVENT-02
|
||||
- EVENT-03
|
||||
- EVENT-04
|
||||
- EVENT-05
|
||||
---
|
||||
|
||||
# Phase 10 - Events Research
|
||||
|
||||
## Research Complete
|
||||
|
||||
Phase 10 should add a first functional events slice to the Go + HTMX application: tablo-scoped scheduled events, a simple month calendar in a new Events tab, inline create/edit/delete flows, and a user-scoped query surface for Phase 11's individual planning page.
|
||||
|
||||
The implementation should follow the recent Phase 9 etapes pattern: add the database model and SQLC queries first, add protected tablo child routes, render tab-local HTMX fragments, and refresh the whole tab after mutations.
|
||||
|
||||
## Existing System Fit
|
||||
|
||||
### Tablo Detail Tabs
|
||||
|
||||
`backend/templates/tablos.templ` already has the main integration point. `TabloDetailPage` renders the tab navigation and a `#tab-content` target. Existing tabs use HTMX links with `hx-target="#tab-content"`, `hx-swap="innerHTML"`, and `hx-push-url`. Events should be added beside Overview, Tasks, and Files with the same behavior.
|
||||
|
||||
`TasksTabFragment` and `FilesTabFragment` are the closest fragment patterns. Phase 10 should add an `EventsTabFragment` that can be returned by `/tablos/{id}/events` and can also be embedded in the full `TabloDetailPage` when `activeTab == "events"`.
|
||||
|
||||
### Route and Handler Patterns
|
||||
|
||||
`backend/internal/web/router.go` mounts protected tablo child resources below `/tablos/{id}/...`. Static child routes should remain before parametric routes. Events should follow this shape:
|
||||
|
||||
- `GET /tablos/{id}/events` - render Events tab or full page fallback.
|
||||
- `GET /tablos/{id}/events/new` - render inline create form, with optional `date=YYYY-MM-DD` and `month=YYYY-MM`.
|
||||
- `POST /tablos/{id}/events` - create an event and return the refreshed Events tab.
|
||||
- `GET /tablos/{id}/events/{event_id}/edit` - render inline edit form.
|
||||
- `POST /tablos/{id}/events/{event_id}` - update an event and return the refreshed Events tab.
|
||||
- `GET /tablos/{id}/events/{event_id}/delete` - render delete confirmation.
|
||||
- `POST /tablos/{id}/events/{event_id}/delete` - hard delete and return the refreshed Events tab.
|
||||
|
||||
The events handlers should use `loadOwnedTablo` as the parent guard and add a `loadOwnedEvent` helper that queries by both `event_id` and `tablo_id`. Missing or foreign resources should return 404, matching tasks, files, and etapes.
|
||||
|
||||
### Form and Fragment Pattern
|
||||
|
||||
Create/edit/delete should render inline inside the Events tab, likely in a dedicated `#event-form-slot`. Mutation responses should refresh the whole `#events-tab` rather than only a day cell. This matches the reliability pattern from etapes and avoids calendar count/day ordering drift after changes.
|
||||
|
||||
Phase 9 exposed a bug where a URL/search-param filter could override an explicit selected form value. Events should avoid that class of issue: if both a query string date/month and submitted form fields exist, submitted form values are authoritative. Query params are only defaults for rendering a new form.
|
||||
|
||||
## Data Model
|
||||
|
||||
Add a migration after `0007_etapes.sql` for a tablo-owned `events` table.
|
||||
|
||||
Recommended schema:
|
||||
|
||||
```sql
|
||||
CREATE TABLE events (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tablo_id UUID NOT NULL REFERENCES tablos(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
event_date DATE NOT NULL,
|
||||
start_time TIME NOT NULL,
|
||||
end_time TIME,
|
||||
description TEXT,
|
||||
location TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT events_title_not_blank CHECK (length(trim(title)) > 0),
|
||||
CONSTRAINT events_end_after_start CHECK (end_time IS NULL OR end_time > start_time)
|
||||
);
|
||||
|
||||
CREATE INDEX events_tablo_month_idx
|
||||
ON events (tablo_id, event_date, start_time, title);
|
||||
|
||||
CREATE INDEX events_date_idx
|
||||
ON events (event_date, start_time);
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `event_date`, `start_time`, and `end_time` preserve the Phase 10 local-date/local-time decision.
|
||||
- `location` is optional because EVENT-01 and the discussion context include it.
|
||||
- Hard delete means no `deleted_at`.
|
||||
- The database check should reject same-time or backwards end times even if a handler bug slips through.
|
||||
- If the project has an existing updated-at trigger convention, reuse it. Otherwise set `updated_at = now()` in the update query, as existing query files do.
|
||||
|
||||
## SQLC Query Surface
|
||||
|
||||
Create `backend/internal/db/queries/events.sql`.
|
||||
|
||||
Recommended queries:
|
||||
|
||||
- `CreateEvent` inserts a tablo event and returns the row.
|
||||
- `GetEventByID` accepts `id` and `tablo_id` to support parent-scoped authorization.
|
||||
- `ListEventsByTabloRange` accepts `tablo_id`, `start_date`, and `end_date`, ordered by `event_date`, `start_time`, `title`.
|
||||
- `UpdateEvent` accepts `id`, `tablo_id`, and editable fields, sets `updated_at = now()`, and returns the row.
|
||||
- `DeleteEvent` accepts `id` and `tablo_id`.
|
||||
- `ListUserEventsRange` joins `events` to `tablos` where `tablos.user_id = $1` and `events.event_date` is inside a provided range. It should return event fields plus `tablo_title` and `tablo_color` for Phase 11 planning.
|
||||
|
||||
Expected date/time Go types should be confirmed after generation. With pgx-backed SQLC, PostgreSQL `date` and `time` usually generate pgtype-backed values. Add small parse/format helpers in the web layer rather than scattering conversion logic across handlers and templates.
|
||||
|
||||
## Calendar Rendering
|
||||
|
||||
The Events tab should render a restrained month grid. The planner should keep it server-rendered with no client-side calendar dependency.
|
||||
|
||||
Recommended model:
|
||||
|
||||
- Parse `month=YYYY-MM`; default to the current local month when missing.
|
||||
- Compute month start, month end, leading blank cells, and trailing blanks server-side.
|
||||
- List events for the inclusive date range covering the visible month.
|
||||
- Group events by `event_date` before passing data to the template.
|
||||
- Day cells show titles only. Show the first few titles and then `+N more` when the cell has more events.
|
||||
- Previous/next month links should target `/tablos/{id}/events?month=YYYY-MM`.
|
||||
- The general `New event` button should open the create form without a date default.
|
||||
- Day-cell create actions should open `/tablos/{id}/events/new?date=YYYY-MM-DD&month=YYYY-MM`.
|
||||
|
||||
Use a small helper such as `parseCalendarMonth(raw string, now time.Time)` so tests can cover invalid and missing month values without depending on the wall clock.
|
||||
|
||||
## Validation Rules
|
||||
|
||||
Handler validation should cover:
|
||||
|
||||
- Title required after trimming.
|
||||
- Date required and parseable as `YYYY-MM-DD`.
|
||||
- Start time required and parseable as `HH:MM`.
|
||||
- End time optional, parseable as `HH:MM` when present.
|
||||
- End time must be after start time when present.
|
||||
- Description and location optional; apply conservative maximum lengths consistent with local form conventions.
|
||||
|
||||
Templates should use existing field error patterns and clear copy for the end-time rule. The expected message does not need exact product polish, but it should be explicit that the end time must be after the start time.
|
||||
|
||||
## Security Notes
|
||||
|
||||
Threats to account for in the plan:
|
||||
|
||||
- Cross-tablo event access: every event read/update/delete query must include the parent `tablo_id`, and the parent tablo must be loaded through `loadOwnedTablo`.
|
||||
- Cross-user event aggregation: `ListUserEventsRange` must join through `tablos.user_id`, not trust event IDs or tablo IDs supplied by the client.
|
||||
- CSRF: every create/update/delete form must include the existing CSRF token and run through the current middleware.
|
||||
- Stored content display: titles, descriptions, and locations should be rendered through templ so HTML is escaped.
|
||||
- Time validation bypass: enforce end-after-start both in handlers and with a DB check constraint.
|
||||
- Route ambiguity: mount static event routes before parametric routes.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Add DB-backed web tests in the existing style from `handlers_tasks_test.go` and recent Phase 9 tests.
|
||||
|
||||
High-value automated tests:
|
||||
|
||||
- Migration/query test creates a tablo event and lists it by month/range.
|
||||
- Events tab renders in full page mode and HTMX fragment mode.
|
||||
- `GET /tablos/{id}/events?month=YYYY-MM` shows month navigation and event titles.
|
||||
- `GET /tablos/{id}/events/new?date=YYYY-MM-DD` pre-fills the date field.
|
||||
- Creating an event refreshes the Events tab and shows the new title.
|
||||
- Creating with `end_time <= start_time` returns 422 and a visible validation error.
|
||||
- Updating an event changes title/date/time and refreshes the calendar.
|
||||
- Deleting an event removes it from the calendar.
|
||||
- Events owned by another user's tablo return 404 for edit/update/delete.
|
||||
- User-scoped range query returns only events for tablos owned by the authenticated user and includes tablo title/color.
|
||||
|
||||
Manual UAT should still check the calendar interaction flow in a browser because the first version is HTMX-heavy and layout-sensitive.
|
||||
|
||||
## Implementation Risks
|
||||
|
||||
- `NewRouter` signature changes will affect many tests and app wiring. Update all call sites in one task.
|
||||
- SQLC generated files should be regenerated, never hand-edited.
|
||||
- Date/time type conversion can be noisy. Keep helpers small and test them indirectly through handler tests.
|
||||
- Month query params and form field precedence should be explicit to avoid repeating the Phase 9 selected-etape bug.
|
||||
- The UI safety gate applies: this phase adds a visible Events tab and calendar. Planning should not continue to implementation until a `10-UI-SPEC.md` exists.
|
||||
|
||||
## Validation Architecture
|
||||
|
||||
Phase 10 has enough behavioral surface to need automated sampling from the first implementation wave.
|
||||
|
||||
### Test Infrastructure
|
||||
|
||||
- Framework: Go `testing` with existing DB-backed web handler tests.
|
||||
- Quick command: `go test ./backend/internal/web ./backend/internal/db/sqlc`
|
||||
- Full command: `go test ./...`
|
||||
- Expected quick feedback latency: under 20 seconds on the local dev database if migrations are already available.
|
||||
|
||||
### Reference Dataset
|
||||
|
||||
Use generated UUID users/tablos/events in tests. The minimum dataset should include:
|
||||
|
||||
- User A with Tablo A and two events in the selected month.
|
||||
- User A with a second tablo and one event for user-scoped aggregation.
|
||||
- User B with Tablo B and one event in the same date range to prove isolation.
|
||||
- One event with no end time.
|
||||
- One event with description and location.
|
||||
- One invalid submission where end time is equal to start time.
|
||||
|
||||
### Required Coverage Dimensions
|
||||
|
||||
- Persistence: migration, SQLC create/list/update/delete, cascade behavior via tablo delete if practical.
|
||||
- Authorization: parent-tablo ownership for all event mutation and read paths.
|
||||
- Calendar query: month parsing, range boundaries, previous/next month links.
|
||||
- Form behavior: create defaults from day click, submitted form values override query defaults, validation errors retain submitted values.
|
||||
- HTMX integration: tab route returns fragment for HTMX and full page fallback for non-HTMX.
|
||||
- Phase 11 handoff: user-scoped range query returns only accessible events and includes tablo title/color.
|
||||
|
||||
### Sampling Plan
|
||||
|
||||
- After schema/query task: run DB query tests.
|
||||
- After handler task: run focused web handler tests.
|
||||
- After template/tab integration task: run focused web handler tests plus `go test ./backend/templates` if template packages are separately testable; otherwise regenerate templates and run the web tests.
|
||||
- Before UAT: run `go test ./...`.
|
||||
|
||||
Loading…
Reference in a new issue