diff --git a/.planning/STATE.md b/.planning/STATE.md
index 76b45c1..30c7c71 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: Phase 09 UAT passed; security review pending
-last_updated: "2026-05-15T22:12:20.689Z"
-last_activity: 2026-05-15 -- Phase 09 UAT gap resolved and Plan 09-04 closed
+status: ready_to_execute
+last_updated: "2026-05-15T22:18:38.208Z"
+last_activity: 2026-05-15 -- Phase 10 planning complete
progress:
total_phases: 5
completed_phases: 2
- total_plans: 9
+ total_plans: 13
completed_plans: 9
- percent: 100
+ percent: 69
---
# STATE
@@ -24,15 +24,15 @@ progress:
See: `.planning/PROJECT.md` (updated 2026-05-15)
**Core value:** A user can sign in and run the Tablos workflow — organize work, attach files, discuss, and plan scheduled events — without a JS framework or managed chat provider.
-**Current focus:** Phase 09 — etapes
+**Current focus:** Phase 10 — events
## Current Position
-Phase: 09 (etapes) — EXECUTING
-Plan: 4 of 4
-Status: Phase 09 UAT passed; security review pending
-Last activity: 2026-05-15 -- Phase 09 UAT gap resolved and Plan 09-04 closed
-Resume file: .planning/phases/10-events/10-UI-SPEC.md
+Phase: 10 (events) — PLANNED
+Plan: 0 of 4
+Status: Ready to execute
+Last activity: 2026-05-15 -- Phase 10 planning complete
+Resume file: .planning/phases/10-events/10-01-PLAN.md
## Phase Status
@@ -40,7 +40,7 @@ Resume file: .planning/phases/10-events/10-UI-SPEC.md
|---|-------|--------|
| 8 | Social Sign-in | ✓ Complete |
| 9 | Etapes | ◆ UAT passed; security pending |
-| 10 | Events | ○ Pending |
+| 10 | Events | ◆ Ready to execute |
| 11 | Individual Planning | ○ Pending |
| 12 | Native Tablo Chat | ○ Pending |
diff --git a/.planning/phases/10-events/10-01-PLAN.md b/.planning/phases/10-events/10-01-PLAN.md
new file mode 100644
index 0000000..67decaf
--- /dev/null
+++ b/.planning/phases/10-events/10-01-PLAN.md
@@ -0,0 +1,156 @@
+---
+phase: 10-events
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - backend/migrations/0008_events.sql
+ - backend/internal/db/queries/events.sql
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_events.go
+ - backend/internal/web/router.go
+ - backend/cmd/web/main.go
+ - backend/templates/events.templ
+ - backend/templates/events_forms.go
+ - backend/templates/tablos.templ
+autonomous: true
+requirements: [EVENT-01, EVENT-03, EVENT-04, EVENT-05]
+
+must_haves:
+ truths:
+ - "GOAL: A user can schedule events that belong to tablos with the same authorization expectations as tasks and files"
+ - "EVENT-01: User can create a scheduled event attached to a tablo with title, start time, optional end time, optional description, and optional location"
+ - "EVENT-03: Tablo detail page includes an Events view listing that tablo's scheduled events"
+ - "EVENT-04: Event validation requires end_time to be empty or after start_time"
+ - "EVENT-05: Event create/list authorization follows tablo ownership via loadOwnedTablo"
+ - "D-01/D-05: Store local event_date, start_time, and nullable end_time columns, not timestamptz"
+ - "D-02/D-03/D-04: Date/start time are required; end time is optional and display omits end when blank"
+ - "D-06/D-07/D-10/D-17/D-18/D-19: Events tab renders a month grid showing past/future events as titles ordered by start_time then title"
+ - "D-12/D-13/D-15: Inline create form fields refresh the whole Events tab after create"
+ - "UI-SPEC: Events tab uses #events-tab, #event-form-slot, New event, Create event, Close form, and slate/blue/red local UI styling"
+ artifacts:
+ - path: "backend/migrations/0008_events.sql"
+ provides: "events table with local date/time columns, tablo FK, end-after-start check, and ordering indexes"
+ - path: "backend/internal/db/queries/events.sql"
+ provides: "CreateEvent, GetEventByID, and ListEventsByTabloRange"
+ - path: "backend/internal/web/handlers_events.go"
+ provides: "Events tab, create form, create validation, month data loading, and ownership checks"
+ - path: "backend/templates/events.templ"
+ provides: "Events tab month grid and inline create form rendering"
+---
+
+
+Vertical slice 1: a signed-in user can open a tablo's Events tab, create an event with local date/time fields, and see the event title in that tablo's month calendar. This plan establishes the event schema, query surface, route wiring, and first working tab UI.
+
+
+
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-CONTEXT.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-RESEARCH.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-UI-SPEC.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-VALIDATION.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-PATTERNS.md
+
+
+
+T-10-01 Cross-tablo event access: every list/create path must load the parent through `loadOwnedTablo`; child event queries must include `tablo_id`.
+T-10-02 CSRF: create form must include `@ui.CSRFField(csrfToken)` and use the existing POST route behind CSRF middleware.
+T-10-03 XSS: event title, description, and location must render through templ escaped expressions.
+T-10-04 Time validation bypass: create handler and DB check must reject `end_time <= start_time`.
+T-10-05 Route ambiguity: events static routes must be mounted before event parametric routes.
+
+
+
+
+
+ Task 1: Add RED coverage for create-and-list calendar slice
+
+ - backend/internal/web/handlers_events_test.go
+
+
+ - backend/internal/web/handlers_tasks_test.go
+ - backend/internal/web/handlers_etapes_test.go
+ - backend/internal/web/router.go
+ - .planning/phases/10-events/10-VALIDATION.md
+ - .planning/phases/10-events/10-UI-SPEC.md
+
+
+ Add DB-backed handler tests in `handlers_events_test.go` using `setupTestDB`, `preInsertUser`, authenticated cookies, and `getCSRFToken`.
+ Add `TestEventsTabRendersMonthGrid` for `GET /tablos/{id}/events?month=2026-05` expecting `Events`, `May 2026`, weekday labels, and `New event`.
+ Add `TestEventCreateRendersTitleInCalendar` that POSTs `/tablos/{id}/events` with `title=Kickoff`, `event_date=2026-05-20`, `start_time=09:30`, empty `end_time`, `location=Office`, `description=Planning`, then expects the refreshed fragment to contain `Kickoff`.
+ Add `TestEventCreateRejectsEndTimeBeforeOrEqualStart` with `start_time=10:00` and `end_time=10:00`, expecting HTTP 422 and copy `End time must be after the start time.`.
+ Add `TestEventsTabOwnershipReturns404` proving another signed-in user cannot GET the first user's `/tablos/{id}/events`.
+
+
+ cd backend && go test ./internal/web -run 'TestEventsTabRendersMonthGrid|TestEventCreateRendersTitleInCalendar|TestEventCreateRejectsEndTimeBeforeOrEqualStart|TestEventsTabOwnershipReturns404' -count=1
+
+
+ - `backend/internal/web/handlers_events_test.go` contains all four named tests.
+ - Tests fail before implementation because the events route/schema/templates do not exist.
+ - The validation test asserts exact copy `End time must be after the start time.`.
+
+
+
+
+ Task 2: Implement event creation and month listing
+
+ - backend/migrations/0008_events.sql
+ - backend/internal/db/queries/events.sql
+ - backend/internal/web/handlers_events.go
+ - backend/internal/web/router.go
+ - backend/cmd/web/main.go
+ - backend/templates/events.templ
+ - backend/templates/events_forms.go
+ - backend/templates/tablos.templ
+
+
+ - backend/migrations/0003_tablos.sql
+ - backend/migrations/0007_etapes.sql
+ - backend/internal/db/queries/tablos.sql
+ - backend/internal/web/handlers_files.go
+ - backend/internal/web/handlers_etapes.go
+ - backend/internal/web/router.go
+ - backend/templates/files.templ
+ - backend/templates/etapes.templ
+ - backend/templates/tablos.templ
+ - .planning/phases/10-events/10-RESEARCH.md
+ - .planning/phases/10-events/10-UI-SPEC.md
+ - .planning/phases/10-events/10-PATTERNS.md
+
+
+ Add `0008_events.sql` with `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())`, `events_title_not_blank`, `events_end_after_start`, `events_tablo_month_idx`, and `events_date_idx`.
+ Add `events.sql` queries: `CreateEvent`, `GetEventByID`, and `ListEventsByTabloRange`, all parent-scoped by `tablo_id` where applicable and ordered by `event_date`, `start_time`, `title`.
+ Add `EventsDeps{Queries *sqlc.Queries}` and wire it through `NewRouter` plus `backend/cmd/web/main.go` and test helpers. Mount `GET /tablos/{id}/events`, `GET /tablos/{id}/events/new`, and `POST /tablos/{id}/events` under authenticated routes with static event routes before future parametric routes.
+ Add `handlers_events.go` with month parsing for `YYYY-MM`, local current-month default, date/time parsing helpers for `YYYY-MM-DD` and `HH:MM`, create validation, and a `renderEventsTab` helper that returns `EventsTabFragment`.
+ Extend `TabloDetailPage` to include the `Events` nav link and render `EventsTabFragment` when `activeTab == "events"`.
+ Add `events.templ` and `events_forms.go` with `#events-tab`, `#event-form-slot`, month grid, weekday labels, `New event`, `Create event`, `Close form`, and escaped event title rows. Day cells show event titles only; no special empty-month copy.
+ Run `cd backend && just generate` after migration/query/template changes.
+
+
+ cd backend && just generate && go test ./internal/web -run 'TestEventsTabRendersMonthGrid|TestEventCreateRendersTitleInCalendar|TestEventCreateRejectsEndTimeBeforeOrEqualStart|TestEventsTabOwnershipReturns404' -count=1
+
+
+ - `backend/migrations/0008_events.sql` contains `CREATE TABLE events`, `event_date DATE NOT NULL`, `start_time TIME NOT NULL`, `end_time TIME`, and `events_end_after_start`.
+ - `backend/internal/db/queries/events.sql` contains `CreateEvent`, `GetEventByID`, and `ListEventsByTabloRange`.
+ - `backend/internal/web/router.go` mounts `GET /tablos/{id}/events` and `POST /tablos/{id}/events`.
+ - `backend/templates/tablos.templ` contains tab label `Events` and active branch for `activeTab == "events"`.
+ - Targeted tests listed in verify pass.
+
+
+
+
+
+
+Run:
+- `cd backend && just generate`
+- `cd backend && go test ./internal/web -run 'TestEventsTabRendersMonthGrid|TestEventCreateRendersTitleInCalendar|TestEventCreateRejectsEndTimeBeforeOrEqualStart|TestEventsTabOwnershipReturns404' -count=1`
+- `git diff --check`
+
+
+
+- EVENT-01 create path is delivered for all required fields.
+- EVENT-03 Events tab displays created event titles in a month grid.
+- EVENT-04 create validation and DB check reject invalid end times.
+- EVENT-05 create/list access is scoped to the owned tablo.
+
+
diff --git a/.planning/phases/10-events/10-02-PLAN.md b/.planning/phases/10-events/10-02-PLAN.md
new file mode 100644
index 0000000..789a5db
--- /dev/null
+++ b/.planning/phases/10-events/10-02-PLAN.md
@@ -0,0 +1,138 @@
+---
+phase: 10-events
+plan: 02
+type: execute
+wave: 2
+depends_on:
+ - 10-01-PLAN.md
+files_modified:
+ - backend/internal/db/queries/events.sql
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_events.go
+ - backend/internal/web/router.go
+ - backend/templates/events.templ
+ - backend/templates/events_forms.go
+autonomous: true
+requirements: [EVENT-01, EVENT-02, EVENT-04, EVENT-05]
+
+must_haves:
+ truths:
+ - "EVENT-02: User can edit and delete tablo events"
+ - "EVENT-04: Update path also rejects end_time before or equal to start_time"
+ - "EVENT-05: Event edit/update/delete authorization follows parent tablo ownership and returns 404 for inaccessible events"
+ - "D-12/D-14/D-15: Clicking an event title opens inline edit; create/edit/delete refresh the whole Events tab"
+ - "D-13: Edit form exposes required title/date/start time and optional end time/description/location"
+ - "D-16: Delete is hard delete after confirmation; no deleted_at or restore behavior"
+ - "UI-SPEC: Edit submit copy is Save event changes; delete copy uses Delete event?, Delete event, Keep event"
+ artifacts:
+ - path: "backend/internal/web/handlers_events.go"
+ provides: "loadOwnedEvent plus edit, update, delete-confirm, and hard delete handlers"
+ - path: "backend/templates/events.templ"
+ provides: "event title edit triggers, edit form, and delete confirmation fragments"
+ - path: "backend/internal/web/handlers_events_test.go"
+ provides: "edit/delete/ownership and update validation coverage"
+---
+
+
+Vertical slice 2: a user can click an event title in the Events tab, edit all event fields inline, and hard-delete the event after confirmation without leaking or mutating inaccessible events.
+
+
+
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-CONTEXT.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-RESEARCH.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-UI-SPEC.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-PATTERNS.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-01-PLAN.md
+
+
+
+T-10-01 Cross-tablo event access: edit/update/delete must bind `event_id` to `tablo_id` through `GetEventByID`.
+T-10-02 CSRF: update and delete forms must include `@ui.CSRFField(csrfToken)`.
+T-10-03 XSS: edited title, description, and location remain escaped on re-render.
+T-10-04 Time validation bypass: update handler must reject invalid end time and DB check remains in place.
+T-10-06 Data retention mismatch: delete must be hard delete only; do not add `deleted_at`.
+
+
+
+
+
+ Task 1: Add RED coverage for edit, update, delete, and ownership
+
+ - backend/internal/web/handlers_events_test.go
+
+
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_etapes_test.go
+ - backend/templates/events.templ
+ - .planning/phases/10-events/10-UI-SPEC.md
+
+
+ Add failing tests:
+ 1. `TestEventEditRendersInlineForm` creates an event, GETs `/tablos/{id}/events/{event_id}/edit`, and expects `Save event changes`, current title, date, start time, location, and description.
+ 2. `TestEventUpdateChangesCalendarPlacement` posts `/tablos/{id}/events/{event_id}` with changed title/date/time and verifies the refreshed calendar contains the new title and not the old title.
+ 3. `TestEventUpdateRejectsInvalidEndTime` posts equal start/end times and expects 422 plus `End time must be after the start time.`.
+ 4. `TestEventDeleteRemovesFromCalendarAndDatabase` confirms delete, verifies refreshed calendar omits the title, and verifies `GetEventByID` returns no row.
+ 5. `TestEventMutationOwnershipReturns404` verifies non-owner edit/update/delete-confirm/delete attempts return 404.
+
+
+ cd backend && go test ./internal/web -run 'TestEventEditRendersInlineForm|TestEventUpdateChangesCalendarPlacement|TestEventUpdateRejectsInvalidEndTime|TestEventDeleteRemovesFromCalendarAndDatabase|TestEventMutationOwnershipReturns404' -count=1
+
+
+ - `handlers_events_test.go` contains all five named tests.
+ - Tests fail before implementation because edit/update/delete routes or behavior are missing.
+
+
+
+
+ Task 2: Implement inline edit and hard delete
+
+ - backend/internal/db/queries/events.sql
+ - backend/internal/web/handlers_events.go
+ - backend/internal/web/router.go
+ - backend/templates/events.templ
+ - backend/templates/events_forms.go
+
+
+ - backend/internal/db/queries/events.sql
+ - backend/internal/web/handlers_events.go
+ - backend/internal/web/handlers_etapes.go
+ - backend/internal/web/router.go
+ - backend/templates/events.templ
+ - backend/templates/etapes.templ
+ - .planning/phases/10-events/10-UI-SPEC.md
+ - .planning/phases/10-events/10-PATTERNS.md
+
+
+ Add `UpdateEvent` and `DeleteEvent` SQLC queries, each scoped by `id` and `tablo_id`, with `UpdateEvent` setting `updated_at = now()`.
+ Add `loadOwnedEvent` in `handlers_events.go` using `loadOwnedTablo`, `chi.URLParam(r, "event_id")`, `uuid.Parse`, `GetEventByID`, 404 on missing/foreign rows, and 500 on unexpected errors.
+ Mount `GET /tablos/{id}/events/{event_id}/edit`, `POST /tablos/{id}/events/{event_id}`, `GET /tablos/{id}/events/{event_id}/delete-confirm`, and `POST /tablos/{id}/events/{event_id}/delete` after static events routes.
+ Render event titles as HTMX controls targeting `#event-form-slot` with accessible label `Edit event: {title}`. Add edit form and delete confirmation fragments using UI-SPEC copy: `Save event changes`, `Delete event?`, `This removes the event from this tablo. This cannot be undone.`, `Delete event`, and `Keep event`.
+ Reuse create validation helpers for update. On update validation errors, return 422 and re-render the edit form with submitted values. After successful update/delete, refresh `#events-tab` with the currently selected `month` context.
+ Run `cd backend && just generate`.
+
+
+ cd backend && just generate && go test ./internal/web -run 'TestEventEditRendersInlineForm|TestEventUpdateChangesCalendarPlacement|TestEventUpdateRejectsInvalidEndTime|TestEventDeleteRemovesFromCalendarAndDatabase|TestEventMutationOwnershipReturns404' -count=1
+
+
+ - `backend/internal/db/queries/events.sql` contains `UpdateEvent` and `DeleteEvent`.
+ - `backend/internal/web/handlers_events.go` contains `loadOwnedEvent`.
+ - `backend/templates/events.templ` contains `Save event changes`, `Delete event?`, `Delete event`, and `Keep event`.
+ - Delete test proves the row is removed from the database rather than marked with `deleted_at`.
+ - Targeted tests listed in verify pass.
+
+
+
+
+
+
+Run:
+- `cd backend && just generate`
+- `cd backend && go test ./internal/web -run 'TestEventEditRendersInlineForm|TestEventUpdateChangesCalendarPlacement|TestEventUpdateRejectsInvalidEndTime|TestEventDeleteRemovesFromCalendarAndDatabase|TestEventMutationOwnershipReturns404' -count=1`
+- `git diff --check`
+
+
+
+- EVENT-02 is delivered for edit and hard delete.
+- EVENT-04 is enforced on update.
+- EVENT-05 is enforced on all event mutation routes.
+
diff --git a/.planning/phases/10-events/10-03-PLAN.md b/.planning/phases/10-events/10-03-PLAN.md
new file mode 100644
index 0000000..baeaa5a
--- /dev/null
+++ b/.planning/phases/10-events/10-03-PLAN.md
@@ -0,0 +1,134 @@
+---
+phase: 10-events
+plan: 03
+type: execute
+wave: 3
+depends_on:
+ - 10-02-PLAN.md
+files_modified:
+ - backend/internal/db/queries/events.sql
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_events.go
+ - backend/templates/events.templ
+ - backend/templates/events_forms.go
+autonomous: true
+requirements: [EVENT-01, EVENT-03, EVENT-05]
+
+must_haves:
+ truths:
+ - "D-08/D-21: Events tab supports previous/next month navigation with /tablos/{id}/events?month=YYYY-MM"
+ - "D-09: New event can be opened generally or from a day cell with the date prefilled"
+ - "D-10/D-11: Day cells show event titles only, first few titles, and +N more"
+ - "D-20/D-22/D-23: Phase 10 includes user-scoped date-range query surface returning event fields plus tablo title/color"
+ - "EVENT-05: User-scoped aggregation must filter through tablos.user_id and not leak other users' events"
+ - "Research pitfall: submitted form fields must win over date/month query defaults"
+ artifacts:
+ - path: "backend/internal/db/queries/events.sql"
+ provides: "ListUserEventsRange for Phase 11 planning handoff"
+ - path: "backend/internal/web/handlers_events.go"
+ provides: "month navigation, day prefill, and form/query precedence helpers"
+ - path: "backend/templates/events.templ"
+ provides: "previous/next controls, day create controls, and +N more rendering"
+---
+
+
+Vertical slice 3: the Events tab supports month navigation and day-click date prefill, while the database query layer exposes the user-scoped date-range aggregation that Phase 11 needs for individual planning.
+
+
+
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-CONTEXT.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-RESEARCH.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-UI-SPEC.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-PATTERNS.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-02-PLAN.md
+
+
+
+T-10-01 Cross-tablo event access: day prefill and month links must never bypass `loadOwnedTablo`.
+T-10-07 Cross-user aggregation leak: `ListUserEventsRange` must join `tablos` and filter by `tablos.user_id = $1`.
+T-10-08 Query-default confusion: date query params are defaults only; POST body fields must be authoritative.
+T-10-03 XSS: `+N more`, titles, and month/day labels must render as escaped text.
+
+
+
+
+
+ Task 1: Add RED coverage for navigation, prefill, overflow, and aggregation
+
+ - backend/internal/web/handlers_events_test.go
+
+
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_tasks_test.go
+ - backend/templates/events.templ
+ - .planning/phases/10-events/10-RESEARCH.md
+ - .planning/phases/10-events/10-UI-SPEC.md
+
+
+ Add failing tests:
+ 1. `TestEventsMonthNavigationPushesMonthParam` GETs `/tablos/{id}/events?month=2026-05` and expects links for `month=2026-04` and `month=2026-06` plus accessible labels naming those months.
+ 2. `TestEventNewFromDayPrefillsDate` GETs `/tablos/{id}/events/new?date=2026-05-20&month=2026-05` and expects the date input value `2026-05-20`.
+ 3. `TestEventCreateSubmittedDateOverridesQueryDefault` POSTs `/tablos/{id}/events?date=2026-05-20&month=2026-05` with form `event_date=2026-05-21` and verifies the event is stored on `2026-05-21`.
+ 4. `TestEventsCalendarShowsMoreIndicator` creates four events on one date and expects three titles plus `+1 more`.
+ 5. `TestListUserEventsRangeReturnsOnlyOwnedTablos` creates events for two users in the same date range and verifies the user-scoped query returns only the authenticated user's events with `tablo_title` and `tablo_color`.
+
+
+ cd backend && go test ./internal/web -run 'TestEventsMonthNavigationPushesMonthParam|TestEventNewFromDayPrefillsDate|TestEventCreateSubmittedDateOverridesQueryDefault|TestEventsCalendarShowsMoreIndicator|TestListUserEventsRangeReturnsOnlyOwnedTablos' -count=1
+
+
+ - `handlers_events_test.go` contains all five named tests.
+ - The submitted-date test fails before implementation if query defaults override form data.
+ - The user-scoped query test proves foreign-user events are not returned.
+
+
+
+
+ Task 2: Implement navigation, day prefill, overflow display, and planning query
+
+ - backend/internal/db/queries/events.sql
+ - backend/internal/web/handlers_events.go
+ - backend/templates/events.templ
+ - backend/templates/events_forms.go
+
+
+ - backend/internal/db/queries/events.sql
+ - backend/internal/web/handlers_events.go
+ - backend/templates/events.templ
+ - backend/templates/events_forms.go
+ - .planning/phases/10-events/10-CONTEXT.md
+ - .planning/phases/10-events/10-UI-SPEC.md
+
+
+ Add `ListUserEventsRange` to `events.sql`, joining `events` to `tablos`, filtering by `tablos.user_id`, `events.event_date >= $2`, and `events.event_date <= $3`, ordering by `event_date`, `start_time`, `title`, and returning event fields plus `tablo_title` and `tablo_color`.
+ Extend month model generation to calculate previous and next `YYYY-MM` links, in-month day metadata, and per-day event truncation at three titles with `+N more`.
+ Add day-cell create controls targeting `/tablos/{id}/events/new?date=YYYY-MM-DD&month=YYYY-MM` with `aria-label="Create event on YYYY-MM-DD"`.
+ Implement `EventNewFormHandler` so query `date` pre-populates the create form date and query `month` is carried as calendar context. Ensure POST create/update reads only submitted form fields for `event_date`, `start_time`, and `end_time`; month query controls the refresh view only.
+ Run `cd backend && just generate`.
+
+
+ cd backend && just generate && go test ./internal/web -run 'TestEventsMonthNavigationPushesMonthParam|TestEventNewFromDayPrefillsDate|TestEventCreateSubmittedDateOverridesQueryDefault|TestEventsCalendarShowsMoreIndicator|TestListUserEventsRangeReturnsOnlyOwnedTablos' -count=1
+
+
+ - `backend/internal/db/queries/events.sql` contains `ListUserEventsRange` and `tablos.user_id`.
+ - `backend/templates/events.templ` contains `+` more rendering logic and day create labels.
+ - `EventNewFormHandler` pre-fills date from query only on GET.
+ - POST create/update handlers use submitted `event_date`, not query `date`, for persistence.
+ - Targeted tests listed in verify pass.
+
+
+
+
+
+
+Run:
+- `cd backend && just generate`
+- `cd backend && go test ./internal/web -run 'TestEventsMonthNavigationPushesMonthParam|TestEventNewFromDayPrefillsDate|TestEventCreateSubmittedDateOverridesQueryDefault|TestEventsCalendarShowsMoreIndicator|TestListUserEventsRangeReturnsOnlyOwnedTablos' -count=1`
+- `git diff --check`
+
+
+
+- Month navigation and day-click create prefill are delivered.
+- Calendar cells show titles only and handle overflow with `+N more`.
+- Phase 11 can query the authenticated user's events across owned tablos by date range with tablo title/color.
+
+
diff --git a/.planning/phases/10-events/10-04-PLAN.md b/.planning/phases/10-events/10-04-PLAN.md
new file mode 100644
index 0000000..351ad3e
--- /dev/null
+++ b/.planning/phases/10-events/10-04-PLAN.md
@@ -0,0 +1,185 @@
+---
+phase: 10-events
+plan: 04
+type: execute
+wave: 4
+depends_on:
+ - 10-03-PLAN.md
+files_modified:
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_tablos_test.go
+ - backend/internal/web/handlers_tasks_test.go
+ - backend/templates/events.templ
+ - backend/templates/tablos.templ
+autonomous: false
+requirements: [EVENT-01, EVENT-02, EVENT-03, EVENT-04, EVENT-05]
+
+must_haves:
+ truths:
+ - "All EVENT-01..05 requirements are covered by automated tests or the final browser checkpoint"
+ - "D-01 through D-23 are represented by source behavior, tests, or the final browser checkpoint"
+ - "UI-SPEC: Events tab, toolbar, month grid, inline forms, copy, colors, typography, spacing, and accessibility labels are visible"
+ - "Validation Architecture: final full suite and browser UAT run before verify-work"
+ artifacts:
+ - path: "backend/internal/web/handlers_events_test.go"
+ provides: "final regression coverage for events"
+ - path: "backend/templates/events.templ"
+ provides: "final UI contract adjustments"
+ - path: "backend/templates/tablos.templ"
+ provides: "final tab integration adjustments"
+---
+
+
+Vertical slice 4: harden the completed Events feature with regression tests, full generated-code verification, and browser UAT against all event requirements, context decisions, UI contract, and security expectations.
+
+
+
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-CONTEXT.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-RESEARCH.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-UI-SPEC.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-VALIDATION.md
+@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-03-PLAN.md
+
+
+
+T-10-01 Cross-tablo event access remains covered by owner-only handler tests.
+T-10-02 CSRF remains covered by mutation tests using real CSRF tokens.
+T-10-03 XSS remains covered by templ escaped rendering and should be spot-checked with HTML-looking event content.
+T-10-04 Time validation remains covered on create and update plus DB check.
+T-10-07 Cross-user aggregation leak remains covered by `ListUserEventsRange` tests.
+T-10-09 Regression risk: NewRouter signature changes can silently break existing tablo/task/file/auth tests if not run together.
+
+
+
+SOURCE | ID | Feature/Requirement | Plan | Status | Notes
+GOAL | - | Tablo-owned scheduled events with task/file-level authorization | 10-01,10-02,10-03,10-04 | COVERED | Schema, handlers, ownership tests, final UAT
+REQ | EVENT-01 | Create event with title, start time, optional end time, description, location | 10-01,10-03,10-04 | COVERED | Create form, validation, submitted-value precedence
+REQ | EVENT-02 | Edit and delete events | 10-02,10-04 | COVERED | Inline edit and hard delete
+REQ | EVENT-03 | Tablo detail events view listing scheduled events | 10-01,10-03,10-04 | COVERED | Events tab month grid
+REQ | EVENT-04 | End time empty or after start time | 10-01,10-02,10-04 | COVERED | Handler validation and DB check
+REQ | EVENT-05 | Authorization follows tablo access rules | 10-01,10-02,10-03,10-04 | COVERED | Parent ownership and aggregation filtering
+CONTEXT | D-01..D-05 | Local date/time storage and display assumptions | 10-01,10-02,10-04 | COVERED | date/time columns and helpers
+CONTEXT | D-06..D-11 | Events tab month grid, navigation, day create, title-only cells, +N more | 10-01,10-03,10-04 | COVERED | Calendar UI tasks
+CONTEXT | D-12..D-15 | Inline create/edit flow and full tab refresh | 10-01,10-02,10-04 | COVERED | `#event-form-slot`, `#events-tab`
+CONTEXT | D-16..D-19 | Hard delete, past visibility, ordering, no special empty state | 10-01,10-02,10-04 | COVERED | Query/order/delete tasks
+CONTEXT | D-20..D-23 | Phase 11 user-scoped date-range query with tablo metadata | 10-03,10-04 | COVERED | `ListUserEventsRange`
+RESEARCH | Data model | `events` table with local date/time columns and indexes | 10-01 | COVERED | Migration task
+RESEARCH | Security | Parent-scoped queries, CSRF, escaping, DB check | 10-01,10-02,10-03,10-04 | COVERED | Threat models and tests
+RESEARCH | Testing | DB-backed create/list/update/delete/ownership/aggregation tests | 10-01,10-02,10-03,10-04 | COVERED | Per-plan RED tests
+UI-SPEC | Contract | Events tab, calendar, forms, copy, a11y, registry safety | 10-01,10-02,10-03,10-04 | COVERED | Implemented and UAT checked
+
+
+
+
+
+ Task 1: Add final regression coverage for tab integration and escaping
+
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_tablos_test.go
+
+
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_tablos_test.go
+ - backend/templates/events.templ
+ - backend/templates/tablos.templ
+ - .planning/phases/10-events/10-UI-SPEC.md
+ - .planning/phases/10-events/10-VALIDATION.md
+
+
+ Add final regression tests:
+ 1. `TestTabloDetailEventsTabFullPageFallback` verifies non-HTMX `/tablos/{id}/events?month=2026-05` renders the full detail shell with Events active and includes Overview, Tasks, Files, and Events tabs.
+ 2. `TestEventContentIsEscaped` creates an event with title `` and verifies rendered HTML does not contain a raw script tag while still displaying escaped title text.
+ 3. `TestEventWithoutEndTimeDisplaysOnlyStartTime` verifies an event with blank end time displays `09:30` and does not render an inferred range.
+ 4. `TestEventsOrderedByStartTimeThenTitle` creates same-day events and verifies calendar title order follows start time ascending, then title.
+
+
+ cd backend && go test ./internal/web -run 'TestTabloDetailEventsTabFullPageFallback|TestEventContentIsEscaped|TestEventWithoutEndTimeDisplaysOnlyStartTime|TestEventsOrderedByStartTimeThenTitle' -count=1
+
+
+ - `handlers_events_test.go` contains escaping, no-end-time display, and ordering tests.
+ - `handlers_tablos_test.go` or `handlers_events_test.go` contains the full-page fallback test.
+ - Targeted tests listed in verify pass.
+
+
+
+
+ Task 2: Run full backend verification and fix regressions
+
+ - backend/internal/web/handlers_events.go
+ - backend/internal/web/router.go
+ - backend/templates/events.templ
+ - backend/templates/tablos.templ
+ - backend/internal/web/handlers_events_test.go
+ - backend/internal/web/handlers_tasks_test.go
+ - backend/internal/web/handlers_tablos_test.go
+
+
+ - backend/internal/web/router.go
+ - backend/internal/web/handlers_events.go
+ - backend/templates/events.templ
+ - backend/templates/tablos.templ
+ - .planning/phases/10-events/10-RESEARCH.md
+ - .planning/phases/10-events/10-UI-SPEC.md
+ - .planning/phases/10-events/10-VALIDATION.md
+
+
+ Run generated code and full backend tests. Fix any regressions caused by the new `EventsDeps` router signature, generated SQLC types, templ signatures, route ordering, or tab rendering.
+ Keep fixes scoped to Phase 10 files and affected test helpers. Do not alter Phase 8 social sign-in behavior, Phase 9 etape behavior, or existing task/file route semantics except where router call sites require the new dependency parameter.
+
+
+ cd backend && just generate && go test ./... -count=1
+
+
+ - `cd backend && just generate` exits 0.
+ - `cd backend && go test ./... -count=1` exits 0.
+ - `git diff --check` exits 0.
+ - Existing task, etape, file, auth, and tablo tests still pass with the new router signature.
+
+
+
+
+ Task 3: Browser UAT checkpoint for Events tab
+
+
+ - .planning/phases/10-events/10-UI-SPEC.md
+ - .planning/phases/10-events/10-VALIDATION.md
+ - backend/templates/events.templ
+
+
+ Start the app with `just dev` from the repository root or `cd backend && just dev`, sign in, open an existing tablo, and verify the Events tab manually:
+ 1. Open Events and confirm the month grid, `Previous month`, `Next month`, and `New event` controls render without overlap.
+ 2. Create an event from `New event` with title, date, start time, optional end time, description, and location.
+ 3. Create another event from a day cell and confirm the date is prefilled.
+ 4. Edit an event title/date/time and confirm the calendar refreshes to the correct month.
+ 5. Try equal start/end times and confirm the visible error copy is `End time must be after the start time.`.
+ 6. Delete an event and confirm it disappears after `Delete event`.
+ 7. Navigate to a past month with events and confirm past events remain visible.
+
+
+ cd backend && go test ./... -count=1
+ Browser confirms all EVENT-01..05 success criteria and the 10-UI-SPEC interaction contract.
+
+
+ - Full backend tests pass before the browser checkpoint.
+ - Browser verification covers create, edit, delete, invalid end time, month navigation, day-cell prefill, and past-month visibility.
+ - User approves the checkpoint before Phase 10 is marked complete.
+
+
+
+
+
+
+Run:
+- `cd backend && just generate`
+- `cd backend && go test ./... -count=1`
+- `git diff --check`
+- Browser UAT from Task 3
+
+
+
+- All EVENT-01..05 requirements are complete.
+- All D-01..D-23 context decisions are implemented or verified.
+- UI-SPEC is satisfied by the first functional Events tab UI.
+- Security expectations are covered by parent-scoped queries, CSRF forms, escaped rendering, and owner-only tests.
+
+