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

<objective>
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.
</objective>

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

<threat_model>
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.
</threat_model>

<tasks>

<task type="auto">
  <name>Task 1: Add RED coverage for create-and-list calendar slice</name>
  <files>
    - backend/internal/web/handlers_events_test.go
  </files>
  <read_first>
    - 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
  </read_first>
  <action>
    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`.
  </action>
  <verify>
    <automated>cd backend && go test ./internal/web -run 'TestEventsTabRendersMonthGrid|TestEventCreateRendersTitleInCalendar|TestEventCreateRejectsEndTimeBeforeOrEqualStart|TestEventsTabOwnershipReturns404' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `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.`.
  </acceptance_criteria>
</task>

<task type="auto">
  <name>Task 2: Implement event creation and month listing</name>
  <files>
    - 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
  </files>
  <read_first>
    - 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
  </read_first>
  <action>
    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.
  </action>
  <verify>
    <automated>cd backend && just generate && go test ./internal/web -run 'TestEventsTabRendersMonthGrid|TestEventCreateRendersTitleInCalendar|TestEventCreateRejectsEndTimeBeforeOrEqualStart|TestEventsTabOwnershipReturns404' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `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.
  </acceptance_criteria>
</task>

</tasks>

<verification>
Run:
- `cd backend && just generate`
- `cd backend && go test ./internal/web -run 'TestEventsTabRendersMonthGrid|TestEventCreateRendersTitleInCalendar|TestEventCreateRejectsEndTimeBeforeOrEqualStart|TestEventsTabOwnershipReturns404' -count=1`
- `git diff --check`
</verification>

<success_criteria>
- 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.
</success_criteria>

