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

<objective>
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.
</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-PATTERNS.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/10-events/10-01-PLAN.md
</context>

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

<tasks>

<task type="auto">
  <name>Task 1: Add RED coverage for edit, update, delete, and ownership</name>
  <files>
    - backend/internal/web/handlers_events_test.go
  </files>
  <read_first>
    - 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
  </read_first>
  <action>
    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.
  </action>
  <verify>
    <automated>cd backend && go test ./internal/web -run 'TestEventEditRendersInlineForm|TestEventUpdateChangesCalendarPlacement|TestEventUpdateRejectsInvalidEndTime|TestEventDeleteRemovesFromCalendarAndDatabase|TestEventMutationOwnershipReturns404' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `handlers_events_test.go` contains all five named tests.
    - Tests fail before implementation because edit/update/delete routes or behavior are missing.
  </acceptance_criteria>
</task>

<task type="auto">
  <name>Task 2: Implement inline edit and hard delete</name>
  <files>
    - 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
  </files>
  <read_first>
    - 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
  </read_first>
  <action>
    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`.
  </action>
  <verify>
    <automated>cd backend && just generate && go test ./internal/web -run 'TestEventEditRendersInlineForm|TestEventUpdateChangesCalendarPlacement|TestEventUpdateRejectsInvalidEndTime|TestEventDeleteRemovesFromCalendarAndDatabase|TestEventMutationOwnershipReturns404' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `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.
  </acceptance_criteria>
</task>

</tasks>

<verification>
Run:
- `cd backend && just generate`
- `cd backend && go test ./internal/web -run 'TestEventEditRendersInlineForm|TestEventUpdateChangesCalendarPlacement|TestEventUpdateRejectsInvalidEndTime|TestEventDeleteRemovesFromCalendarAndDatabase|TestEventMutationOwnershipReturns404' -count=1`
- `git diff --check`
</verification>

<success_criteria>
- EVENT-02 is delivered for edit and hard delete.
- EVENT-04 is enforced on update.
- EVENT-05 is enforced on all event mutation routes.
</success_criteria>
