From 0e6347ef26312a7140557fde763be0c53d5f6ebc Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Fri, 15 May 2026 22:30:18 +0200 Subject: [PATCH] docs(09): create phase plan --- .planning/STATE.md | 18 +-- .planning/phases/09-etapes/09-01-PLAN.md | 153 ++++++++++++++++++++++ .planning/phases/09-etapes/09-02-PLAN.md | 130 ++++++++++++++++++ .planning/phases/09-etapes/09-03-PLAN.md | 131 ++++++++++++++++++ .planning/phases/09-etapes/09-04-PLAN.md | 147 +++++++++++++++++++++ .planning/phases/09-etapes/09-PATTERNS.md | 56 ++++++++ 6 files changed, 626 insertions(+), 9 deletions(-) create mode 100644 .planning/phases/09-etapes/09-01-PLAN.md create mode 100644 .planning/phases/09-etapes/09-02-PLAN.md create mode 100644 .planning/phases/09-etapes/09-03-PLAN.md create mode 100644 .planning/phases/09-etapes/09-04-PLAN.md create mode 100644 .planning/phases/09-etapes/09-PATTERNS.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 7a88648..2a54c1d 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: planning -last_updated: "2026-05-15T20:25:48.078Z" -last_activity: 2026-05-15 -- Phase 09 UI-SPEC approved +status: executing +last_updated: "2026-05-15T20:29:51.890Z" +last_activity: 2026-05-15 -- Phase 09 planning complete progress: total_phases: 5 completed_phases: 1 - total_plans: 5 + total_plans: 9 completed_plans: 5 - percent: 100 + percent: 56 --- # STATE @@ -30,16 +30,16 @@ See: `.planning/PROJECT.md` (updated 2026-05-15) Phase: 9 — Etapes Plan: — -Status: Ready for planning -Last activity: 2026-05-15 -- Phase 09 UI-SPEC approved -Resume file: .planning/phases/09-etapes/09-UI-SPEC.md +Status: Ready to execute +Last activity: 2026-05-15 -- Phase 09 planning complete +Resume file: .planning/phases/09-etapes/09-01-PLAN.md ## Phase Status | # | Phase | Status | |---|-------|--------| | 8 | Social Sign-in | ✓ Complete | -| 9 | Etapes | ◐ UI-SPEC approved | +| 9 | Etapes | ◆ Ready to execute | | 10 | Events | ○ Pending | | 11 | Individual Planning | ○ Pending | | 12 | Native Tablo Chat | ○ Pending | diff --git a/.planning/phases/09-etapes/09-01-PLAN.md b/.planning/phases/09-etapes/09-01-PLAN.md new file mode 100644 index 0000000..e56bc79 --- /dev/null +++ b/.planning/phases/09-etapes/09-01-PLAN.md @@ -0,0 +1,153 @@ +--- +phase: 09-etapes +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - backend/migrations/0007_etapes.sql + - backend/internal/db/queries/etapes.sql + - backend/internal/db/queries/tasks.sql + - backend/internal/web/handlers_etapes_test.go + - backend/internal/web/handlers_tasks_test.go + - backend/internal/web/handlers_etapes.go + - backend/internal/web/handlers_tasks.go + - backend/internal/web/router.go + - backend/templates/etapes.templ + - backend/templates/tasks.templ + - backend/templates/tablos.templ +autonomous: true +requirements: [ETAPE-01, ETAPE-02, ETAPE-03, ETAPE-04, ETAPE-05, ETAPE-06] + +must_haves: + truths: + - "D-01/D-02/D-03: Tasks tab renders a compact top etape strip with All, Unassigned, and created etape chips with counts" + - "D-05/D-06/D-07: Selecting an etape chip filters the existing four-column kanban without changing TaskColumns" + - "D-08/D-09/D-10: task position remains status-scoped; no task position is scoped by etape" + - "D-11/D-12: task create form includes an Etape selector with No etape and created etapes" + - "D-14/D-15/D-16: migration models etapes as a tablo-owned table with no parent column and tasks.etape_id nullable ON DELETE SET NULL" + - "POST /tablos/{id}/etapes creates an etape and the response makes it visible in the strip" + - "POST /tablos/{id}/tasks can create a task assigned to the active etape" + artifacts: + - path: "backend/migrations/0007_etapes.sql" + provides: "etapes table plus nullable tasks.etape_id foreign key" + - path: "backend/internal/db/queries/etapes.sql" + provides: "sqlc etape CRUD queries" + - path: "backend/templates/etapes.templ" + provides: "EtapeStrip and create form fragments" + - path: "backend/internal/web/handlers_etapes.go" + provides: "first etape create/list/filter handlers" +--- + + +Vertical slice 1: a user can create an etape inside a tablo, see it as a compact chip in the Tasks tab, filter the existing kanban by that etape, and create a task assigned to it from the existing add-task flow. This plan also establishes the schema and generated query surface needed for every later etape slice. + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-CONTEXT.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-RESEARCH.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-UI-SPEC.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-VALIDATION.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-PATTERNS.md + + + +T-09-01 Cross-tablo etape assignment: task create/update must reject an `etape_id` that does not belong to the same tablo as the task. +T-09-02 Cross-user etape access: non-owners must receive 404 from etape routes through `loadOwnedTablo`. +T-09-03 CSRF: every etape/task mutation form must include `@ui.CSRFField(csrfToken)` and use existing CSRF middleware. +T-09-04 XSS: etape titles/descriptions must be rendered through normal templ escaped output, never raw HTML. +T-09-05 Data loss: deleting an etape must use `ON DELETE SET NULL` behavior for tasks, not task deletion. + + + + + + Task 1: Add RED coverage for first etape slice + + - backend/internal/web/handlers_etapes_test.go + - backend/internal/web/handlers_tasks_test.go + + + - backend/internal/web/handlers_tasks_test.go + - backend/internal/web/handlers_tablos_test.go + - backend/migrations/0004_tasks.sql + - .planning/phases/09-etapes/09-VALIDATION.md + + + Add failing DB-backed handler tests for the first slice: + 1. `TestEtapeCreateRendersChipAndCount` creates an owned tablo, POSTs `/tablos/{id}/etapes` with title `Design`, and expects response/body or subsequent Tasks tab render to contain `Design`, `Unassigned`, and `All`. + 2. `TestTaskCreateAssignsEtape` creates an etape, POSTs `/tablos/{id}/tasks` with `etape_id`, and verifies `GetTaskByID` returns that etape id. + 3. `TestEtapeFilterRendersExistingKanbanColumns` creates assigned and unassigned tasks, GETs `/tablos/{id}/tasks?etape={etape_id}`, expects only assigned task title plus all four column labels. + Keep tests in the existing integration style using `setupTestDB`, `preInsertUser`, `getCSRFToken`, and authenticated cookies. + + + cd backend && go test ./internal/web -run 'TestEtapeCreateRendersChipAndCount|TestTaskCreateAssignsEtape|TestEtapeFilterRendersExistingKanbanColumns' -count=1 + + + - `backend/internal/web/handlers_etapes_test.go` contains `TestEtapeCreateRendersChipAndCount`. + - `backend/internal/web/handlers_tasks_test.go` or `handlers_etapes_test.go` contains `TestTaskCreateAssignsEtape`. + - `backend/internal/web/handlers_etapes_test.go` contains `TestEtapeFilterRendersExistingKanbanColumns`. + - The targeted tests fail before implementation because etape schema/routes/query fields are missing. + + + + + Task 2: Implement the thinnest create-filter-assigned-task slice + + - backend/migrations/0007_etapes.sql + - backend/internal/db/queries/etapes.sql + - backend/internal/db/queries/tasks.sql + - backend/internal/web/handlers_etapes.go + - backend/internal/web/handlers_tasks.go + - backend/internal/web/router.go + - backend/templates/etapes.templ + - backend/templates/tasks.templ + - backend/templates/tablos.templ + + + - backend/migrations/0004_tasks.sql + - backend/migrations/0005_files.sql + - backend/internal/db/queries/tasks.sql + - backend/internal/web/handlers_tasks.go + - backend/internal/web/handlers_files.go + - backend/internal/web/router.go + - backend/templates/tasks.templ + - backend/templates/tablos.templ + - .planning/phases/09-etapes/09-UI-SPEC.md + + + Add `backend/migrations/0007_etapes.sql` with `etapes` table (`id`, `tablo_id`, `title`, `description`, `position`, timestamps), `etapes_tablo_id_position_idx`, nullable `tasks.etape_id` referencing `etapes(id) ON DELETE SET NULL`, and `tasks_tablo_id_etape_id_idx`. Do not add `parent_id`. + Add etape sqlc queries for list/create/get/max position. Extend task queries to select and preserve `etape_id`; `InsertTask` accepts nullable etape id; `UpdateTask` accepts nullable etape id. + Add minimal etape create/list/filter handlers and route mounting under the protected `/tablos/{id}` group. Create `EtapeStrip` plus minimal create form; wrap Tasks tab in `#tasks-tab`. + Extend task create handler/form so `etape_id` can be submitted and validated against the same tablo before insert. When active filter is a concrete etape, the add-task form submits that etape id. + Run `cd backend && just generate` after migration/query/template changes. + + + cd backend && just generate && go test ./internal/web -run 'TestEtapeCreateRendersChipAndCount|TestTaskCreateAssignsEtape|TestEtapeFilterRendersExistingKanbanColumns|TestTaskCreate' -count=1 + + + - `backend/migrations/0007_etapes.sql` contains `CREATE TABLE etapes`. + - `backend/migrations/0007_etapes.sql` contains `ON DELETE SET NULL`. + - `backend/migrations/0007_etapes.sql` does not contain `parent_id`. + - `backend/internal/db/queries/etapes.sql` contains `ListEtapesByTablo`, `InsertEtape`, and `GetEtapeByID`. + - `backend/internal/db/queries/tasks.sql` selects `etape_id` in `ListTasksByTablo` and `GetTaskByID`. + - `backend/templates/tablos.templ` renders a `#tasks-tab` wrapper before `@KanbanBoard`. + - Targeted tests listed in verify pass. + + + + + + +Run: +- `cd backend && just generate` +- `cd backend && go test ./internal/web -count=1` +- `git diff --check` + + + +- ETAPE-01 is partially delivered through create + render chip. +- ETAPE-03 is partially delivered through create-task assignment. +- ETAPE-05 is partially delivered through etape filtering over existing columns. +- ETAPE-06 is delivered at schema level by no nested etape relationship. + diff --git a/.planning/phases/09-etapes/09-02-PLAN.md b/.planning/phases/09-etapes/09-02-PLAN.md new file mode 100644 index 0000000..74a4b9d --- /dev/null +++ b/.planning/phases/09-etapes/09-02-PLAN.md @@ -0,0 +1,130 @@ +--- +phase: 09-etapes +plan: 02 +type: execute +wave: 2 +depends_on: + - 09-01-PLAN.md +files_modified: + - backend/internal/db/queries/etapes.sql + - backend/internal/web/handlers_etapes.go + - backend/internal/web/router.go + - backend/templates/etapes.templ + - backend/templates/tablos.templ + - backend/internal/web/handlers_etapes_test.go +autonomous: true +requirements: [ETAPE-01, ETAPE-02, ETAPE-04, ETAPE-05] + +must_haves: + truths: + - "D-01/D-02/D-03/D-04: etape management remains a compact top strip, not a card panel or separate page" + - "D-08: etape order is independent through etapes.position" + - "D-14: deleting an etape keeps tasks and moves them to Unassigned" + - "D-05/D-06: chip active state and counts update after create/edit/delete/reorder" + - "UI-SPEC: delete confirmation copy says tasks stay in the tablo and move to Unassigned" + artifacts: + - path: "backend/internal/web/handlers_etapes.go" + provides: "Etape edit, delete, and reorder handlers" + - path: "backend/templates/etapes.templ" + provides: "Etape edit/delete/reorder fragments" + - path: "backend/internal/web/handlers_etapes_test.go" + provides: "CRUD, delete-unassign, reorder, and ownership tests" +--- + + +Vertical slice 2: a user can manage etapes from the top strip: edit title/description, delete an etape without deleting tasks, and reorder etape chips. After this plan, etapes are a usable organizing surface independent of task assignment form refinements. + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-CONTEXT.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-RESEARCH.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-UI-SPEC.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-PATTERNS.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-01-PLAN.md + + + +T-09-02 Cross-user etape access: all edit/delete/reorder routes must call `loadOwnedTablo` and bind etape id to tablo id. +T-09-03 CSRF: edit/delete/reorder forms must include `@ui.CSRFField(csrfToken)`. +T-09-04 XSS: title and description output must be templ escaped. +T-09-05 Data loss: delete route must delete only from `etapes`; tasks survive through nullable foreign key behavior. + + + + + + Task 1: Add RED coverage for etape management + + - backend/internal/web/handlers_etapes_test.go + + + - backend/internal/web/handlers_etapes_test.go + - backend/internal/web/handlers_tasks_test.go + - backend/internal/web/handlers_files.go + - .planning/phases/09-etapes/09-UI-SPEC.md + + + Add failing tests: + 1. `TestEtapeUpdateChangesTitleAndDescription` posts `/tablos/{id}/etapes/{etape_id}` and verifies rendered/body and DB state. + 2. `TestEtapeDeleteUnassignsTasks` creates an etape and assigned task, posts delete, verifies task row still exists with null etape id. + 3. `TestEtapeReorderPersistsPosition` creates two etapes, posts `/tablos/{id}/etapes/reorder`, and verifies `ListEtapesByTablo` ordering. + 4. `TestEtapeOwnershipReturns404` verifies a non-owner receives 404 for edit/delete/reorder attempts. + + + cd backend && go test ./internal/web -run 'TestEtapeUpdateChangesTitleAndDescription|TestEtapeDeleteUnassignsTasks|TestEtapeReorderPersistsPosition|TestEtapeOwnershipReturns404' -count=1 + + + - `handlers_etapes_test.go` contains all four named tests. + - Tests fail before implementation because update/delete/reorder routes or behavior are missing. + + + + + Task 2: Implement edit, delete-unassign, and reorder + + - backend/internal/db/queries/etapes.sql + - backend/internal/web/handlers_etapes.go + - backend/internal/web/router.go + - backend/templates/etapes.templ + - backend/templates/tablos.templ + + + - backend/internal/db/queries/etapes.sql + - backend/internal/web/handlers_etapes.go + - backend/internal/web/handlers_tasks.go + - backend/templates/etapes.templ + - backend/templates/tasks.templ + - backend/templates/tablos.templ + + + Add `UpdateEtape`, `DeleteEtape`, and `UpdateEtapePosition` queries. Implement GET edit, POST update, GET delete-confirm, POST delete, and POST reorder handlers. + Delete confirmation must use heading `Delete etape?`, body `Tasks in this etape will stay in the tablo and move to Unassigned.`, submit label `Delete etape`, and cancel label `Keep etape`. + Reordering may use explicit up/down controls or a compact Sortable.js strip. If Sortable.js is reused, use a separate group from `kanban` and do not target `.sortable-column`. + After delete/update/reorder, return a Tasks-tab or etape-strip fragment that refreshes chip titles, counts, active state, and kanban contents. + + + cd backend && just generate && go test ./internal/web -run 'TestEtapeUpdateChangesTitleAndDescription|TestEtapeDeleteUnassignsTasks|TestEtapeReorderPersistsPosition|TestEtapeOwnershipReturns404' -count=1 + + + - `backend/internal/db/queries/etapes.sql` contains `UpdateEtape`, `DeleteEtape`, and `UpdateEtapePosition`. + - `backend/templates/etapes.templ` contains `Delete etape?` and `Tasks in this etape will stay in the tablo and move to Unassigned.` + - Non-owner etape management tests expect and receive 404. + - Delete-unassign test proves task row remains after etape deletion. + - Targeted tests listed in verify pass. + + + + + + +Run: +- `cd backend && just generate` +- `cd backend && go test ./internal/web -count=1` +- `git diff --check` + + + +- ETAPE-02 is delivered for edit/delete/reorder. +- ETAPE-04 is delivered through delete-unassign behavior. +- Etape chip order and counts remain usable from the Tasks tab. + diff --git a/.planning/phases/09-etapes/09-03-PLAN.md b/.planning/phases/09-etapes/09-03-PLAN.md new file mode 100644 index 0000000..b8e01cf --- /dev/null +++ b/.planning/phases/09-etapes/09-03-PLAN.md @@ -0,0 +1,131 @@ +--- +phase: 09-etapes +plan: 03 +type: execute +wave: 3 +depends_on: + - 09-02-PLAN.md +files_modified: + - backend/internal/db/queries/tasks.sql + - backend/internal/web/handlers_tasks.go + - backend/internal/web/handlers_files.go + - backend/templates/tasks.templ + - backend/templates/tablos.templ + - backend/internal/web/handlers_tasks_test.go +autonomous: true +requirements: [ETAPE-03, ETAPE-05] + +must_haves: + truths: + - "D-11/D-12: task create and edit forms include an Etape selector with No etape" + - "D-13: quick dropdowns on task cards are not implemented in this phase" + - "D-05/D-06: filters support concrete etape ids and Unassigned" + - "D-09/D-10: task update preserves current status and position while changing assignment" + - "UI-SPEC: selector label is Etape and empty option is No etape" + artifacts: + - path: "backend/templates/tasks.templ" + provides: "Etape selector in task create/edit fragments" + - path: "backend/internal/web/handlers_tasks.go" + provides: "task assignment parsing and same-tablo validation" + - path: "backend/internal/web/handlers_files.go" + provides: "Tasks tab handler updated to load etapes and filtered tasks" +--- + + +Vertical slice 3: a user can assign and unassign tasks from etapes in the task create/edit forms, and the Tasks tab filter works for All, Unassigned, and each etape while preserving the existing kanban layout. + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-CONTEXT.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-RESEARCH.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-UI-SPEC.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-PATTERNS.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-02-PLAN.md + + + +T-09-01 Cross-tablo etape assignment: create/update handlers must validate submitted etape id with `GetEtapeByID(id, tablo_id)` before writing it to a task. +T-09-02 Cross-user access: filtering by an etape id from another user's tablo must return 404 or ignore it without leaking the etape title. +T-09-03 CSRF: task create/edit forms continue to include CSRF tokens after adding the selector. +T-09-04 XSS: etape option labels must be templ escaped. + + + + + + Task 1: Add RED coverage for assignment and filters + + - backend/internal/web/handlers_tasks_test.go + + + - backend/internal/web/handlers_tasks_test.go + - backend/internal/web/handlers_etapes_test.go + - backend/templates/tasks.templ + - .planning/phases/09-etapes/09-UI-SPEC.md + + + Add failing tests: + 1. `TestTaskEditRendersEtapeSelector` verifies GET edit renders label `Etape`, option `No etape`, and existing etape title. + 2. `TestTaskUpdateAssignsAndUnassignsEtape` posts task update with an etape id, verifies DB assignment, then posts with empty etape value and verifies null assignment. + 3. `TestTaskAssignmentRejectsForeignEtape` creates two tablos and verifies assigning a task to another tablo's etape fails without updating the task. + 4. `TestTasksTabUnassignedFilter` verifies `/tablos/{id}/tasks?etape=unassigned` renders unassigned tasks and hides assigned task titles while keeping all four status headers. + + + cd backend && go test ./internal/web -run 'TestTaskEditRendersEtapeSelector|TestTaskUpdateAssignsAndUnassignsEtape|TestTaskAssignmentRejectsForeignEtape|TestTasksTabUnassignedFilter' -count=1 + + + - `handlers_tasks_test.go` contains all four named tests. + - Tests fail before implementation because selector/update/filter behavior is incomplete. + + + + + Task 2: Implement form assignment and filter-aware task loading + + - backend/internal/db/queries/tasks.sql + - backend/internal/web/handlers_tasks.go + - backend/internal/web/handlers_files.go + - backend/templates/tasks.templ + - backend/templates/tablos.templ + + + - backend/internal/db/queries/tasks.sql + - backend/internal/web/handlers_tasks.go + - backend/internal/web/handlers_files.go + - backend/templates/tasks.templ + - backend/templates/tablos.templ + - .planning/phases/09-etapes/09-RESEARCH.md + - .planning/phases/09-etapes/09-UI-SPEC.md + + + Extend task create/edit template signatures to receive etapes and selected etape id. Render a select labeled `Etape` with first option `No etape`. + Extend `TaskCreateHandler`, `TaskEditHandler`, and `TaskUpdateHandler` to load etapes for the owned tablo, parse optional `etape_id`, validate same-tablo ownership, and preserve existing status/position. + Update `TabloTasksTabHandler` and full detail rendering to load etapes, counts, and current filter. Support `?etape=unassigned` and `?etape={uuid}`. Use existing four kanban columns for filtered task slices. + Do not add quick assignment dropdowns on task cards. + + + cd backend && just generate && go test ./internal/web -run 'TestTaskEditRendersEtapeSelector|TestTaskUpdateAssignsAndUnassignsEtape|TestTaskAssignmentRejectsForeignEtape|TestTasksTabUnassignedFilter|TestTaskUpdate|TestTasksKanbanRenders' -count=1 + + + - `backend/templates/tasks.templ` contains visible label text `Etape` and option text `No etape`. + - `backend/internal/web/handlers_tasks.go` validates submitted etape ids against the current tablo before insert/update. + - `TaskUpdateHandler` still preserves existing status and position. + - `TabloTasksTabHandler` supports `etape=unassigned` and concrete etape UUID filters. + - Targeted tests listed in verify pass. + + + + + + +Run: +- `cd backend && just generate` +- `cd backend && go test ./internal/web -count=1` +- `git diff --check` + + + +- ETAPE-03 is fully delivered. +- ETAPE-05 filtering behavior is delivered for All, Unassigned, and specific etapes. +- Existing task create/update tests still pass. + diff --git a/.planning/phases/09-etapes/09-04-PLAN.md b/.planning/phases/09-etapes/09-04-PLAN.md new file mode 100644 index 0000000..4b4258c --- /dev/null +++ b/.planning/phases/09-etapes/09-04-PLAN.md @@ -0,0 +1,147 @@ +--- +phase: 09-etapes +plan: 04 +type: execute +wave: 4 +depends_on: + - 09-03-PLAN.md +files_modified: + - backend/internal/web/handlers_tasks.go + - backend/templates/tasks.templ + - backend/templates/etapes.templ + - backend/internal/web/handlers_tasks_test.go + - backend/internal/web/handlers_etapes_test.go +autonomous: false +requirements: [ETAPE-01, ETAPE-02, ETAPE-03, ETAPE-04, ETAPE-05, ETAPE-06] + +must_haves: + truths: + - "D-08/D-09/D-10: drag/drop reorder preserves etape_id and remains status-scoped" + - "D-01 through D-16: all context decisions are represented in source behavior or verified manual behavior" + - "UI-SPEC: top strip, chips, active state, selector copy, and delete copy are visually present" + - "Research Pitfall 2: every UpdateTask call preserves etape_id unless assignment is intentionally changing" + artifacts: + - path: "backend/internal/web/handlers_tasks.go" + provides: "reorder paths that preserve etape assignment" + - path: "backend/internal/web/handlers_tasks_test.go" + provides: "filtered reorder and etape preservation regression tests" +--- + + +Vertical slice 4: harden the completed etape feature by proving drag/drop reorder still works with etape filters and assignments, then run final automated and browser verification. This closes the phase against all ETAPE requirements and the UI contract. + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-CONTEXT.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-RESEARCH.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-UI-SPEC.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-VALIDATION.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/09-etapes/09-03-PLAN.md + + + +T-09-01 Cross-tablo assignment remains covered by prior tests and must not regress. +T-09-05 Data loss remains covered by delete-unassign test and browser verification. +T-09-06 Integrity regression: task reorder must never clear or change `etape_id` unless assignment form submitted that change. + + + + + + Task 1: Add reorder preservation regression tests + + - backend/internal/web/handlers_tasks_test.go + + + - backend/internal/web/handlers_tasks_test.go + - backend/internal/web/handlers_tasks.go + - .planning/phases/09-etapes/09-RESEARCH.md + + + Add failing regression tests: + 1. `TestTaskReorderPreservesEtapeAssignment` creates assigned task, posts single-task reorder to another status, and verifies `etape_id` remains unchanged. + 2. `TestFilteredTaskReorderPreservesHiddenTasks` creates tasks in the same status assigned to different etapes, submits reorder for only one visible filtered task, and verifies the hidden task still exists with its original `etape_id`. + 3. `TestTaskOrderPersistsWithEtapeFilter` verifies filtered board ordering remains stable after reload. + + + cd backend && go test ./internal/web -run 'TestTaskReorderPreservesEtapeAssignment|TestFilteredTaskReorderPreservesHiddenTasks|TestTaskOrderPersistsWithEtapeFilter' -count=1 + + + - `handlers_tasks_test.go` contains all three named regression tests. + - Tests fail before implementation if reorder does not preserve `etape_id`. + + + + + Task 2: Preserve etape assignment through reorder and refresh counts + + - backend/internal/web/handlers_tasks.go + - backend/templates/tasks.templ + - backend/templates/etapes.templ + + + - backend/internal/web/handlers_tasks.go + - backend/templates/tasks.templ + - backend/templates/etapes.templ + - .planning/phases/09-etapes/09-RESEARCH.md + + + Update every `UpdateTaskParams` call in `TaskReorderHandler` to pass the existing task's `etape_id`. Confirm the single-task path and array Sortable.js path both preserve assignment. + Ensure reorder responses under an active etape filter return the filtered board or whole Tasks tab consistently enough that chip counts and active state remain correct after HTMX settles. + Keep task position assignment status-scoped; do not add `(etape_id, status, position)` ordering semantics. + + + cd backend && just generate && go test ./internal/web -run 'TestTaskReorderPreservesEtapeAssignment|TestFilteredTaskReorderPreservesHiddenTasks|TestTaskOrderPersistsWithEtapeFilter|TestTaskReorderCrossColumn|TestTaskReorderSameColumn|TestTaskOrderPersists' -count=1 + + + - Every `UpdateTaskParams` call in `handlers_tasks.go` includes the current `EtapeID` or intentional parsed assignment value. + - `TaskReorderHandler` does not parse or write assignment from drag/drop payload. + - Reorder regression tests and existing reorder tests pass. + + + + + Task 3: Final automated and browser verification checkpoint + + + - .planning/phases/09-etapes/09-UI-SPEC.md + - .planning/phases/09-etapes/09-VALIDATION.md + - backend/internal/web/handlers_tasks_test.go + - backend/internal/web/handlers_etapes_test.go + + + Run full generated-code and backend verification, then perform browser verification: + 1. `cd backend && just generate` + 2. `cd backend && go test ./... -count=1` + 3. `git diff --check` + 4. Start `cd backend && just dev`. + 5. In a browser, open a tablo Tasks tab, create two etapes, create assigned and unassigned tasks, filter by an etape, filter by `Unassigned`, edit assignment from the task form, drag a filtered task across columns, delete an etape, and confirm affected tasks move to Unassigned rather than disappearing. + + + cd backend && just generate && go test ./... -count=1 + Browser confirms all ETAPE-01..06 success criteria and UI-SPEC interaction contract. + + + - `cd backend && go test ./... -count=1` exits 0. + - `git diff --check` exits 0. + - Browser verification confirms top-strip chips, counts, filters, assignment selector, reorder, and delete-unassign behavior. + - User approves the checkpoint before Phase 9 is marked complete. + + + + + + +Run: +- `cd backend && just generate` +- `cd backend && go test ./... -count=1` +- `git diff --check` +- browser verification from Task 3 + + + +- All ETAPE-01..06 requirements are complete. +- Existing kanban status and ordering behavior remains intact. +- UI-SPEC is satisfied in the first functional UI. +- Human verification checkpoint is approved. + diff --git a/.planning/phases/09-etapes/09-PATTERNS.md b/.planning/phases/09-etapes/09-PATTERNS.md new file mode 100644 index 0000000..4daeb9f --- /dev/null +++ b/.planning/phases/09-etapes/09-PATTERNS.md @@ -0,0 +1,56 @@ +# Phase 09 - Pattern Map + +## Purpose + +Concrete code patterns Phase 9 should reuse when adding etapes to the Go/HTMX task board. + +## Closest Analogs + +### Schema And SQLC + +- `backend/migrations/0004_tasks.sql` - task enum/table/index pattern; down migration order matters. +- `backend/migrations/0005_files.sql` - tablo-owned child table pattern for resources under a tablo. +- `backend/internal/db/queries/tasks.sql` - current task CRUD query shape and `GetTaskByID` ownership binding. +- `backend/internal/db/queries/files.sql` - tablo-scoped file query analog if present during execution. + +### Handlers + +- `backend/internal/web/handlers_tasks.go` - task CRUD, inline HTMX fragments, status parsing, reorder behavior. +- `backend/internal/web/handlers_files.go` - additional tablo-scoped resource handlers and ownership helpers. +- `backend/internal/web/handlers_tablos.go` - canonical `loadOwnedTablo` helper and 404-on-authz-failure pattern. + +### Templates + +- `backend/templates/tasks.templ` - kanban board, task card, create/edit/delete fragments, OOB add-slot reset. +- `backend/templates/tablos.templ` - tab content boundary and `TasksTabFragment` integration point. +- `backend/internal/web/ui/button.css` and `backend/internal/web/ui` helpers - existing button/card/badge styling. + +### Tests + +- `backend/internal/web/handlers_tasks_test.go` - DB-backed handler tests for task create/update/reorder/delete/ownership. +- `backend/internal/web/handlers_files_test.go` - useful analog for tablo-scoped resource ownership tests if present during execution. + +## Reuse Rules + +- Keep `TaskColumns` and the current kanban column DOM stable. +- New etape routes should live under `/tablos/{id}/...` and call `loadOwnedTablo` before any etape lookup. +- Etape-specific lookup must bind both `id` and `tablo_id`, mirroring `GetTaskByID`. +- HTMX filter operations should swap `#tasks-tab` or `#tab-content`, not just `#kanban-board`, so chip active state and counts stay accurate. +- Task reorder must preserve `etape_id` once the task row grows that field. +- Use `just generate` after migration, sqlc query, or templ changes. + +## Planned File Map + +| Target file | Role | Closest analog | +|-------------|------|----------------| +| `backend/migrations/0007_etapes.sql` | Add etape table and nullable task assignment | `backend/migrations/0004_tasks.sql`, `backend/migrations/0005_files.sql` | +| `backend/internal/db/queries/etapes.sql` | Etape CRUD and position queries | `backend/internal/db/queries/tasks.sql` | +| `backend/internal/db/queries/tasks.sql` | Add nullable etape assignment and filtered task lists | existing same file | +| `backend/internal/web/handlers_etapes.go` | Etape CRUD/reorder handlers | `backend/internal/web/handlers_tasks.go`, `backend/internal/web/handlers_files.go` | +| `backend/internal/web/handlers_tasks.go` | Task assignment and filter-aware task loading | existing same file | +| `backend/internal/web/router.go` | Mount etape routes and preserve task routes | existing task/file route groups | +| `backend/templates/etapes.templ` | Etape strip, chips, forms, delete confirmation | `backend/templates/tasks.templ` | +| `backend/templates/tasks.templ` | Add etape selector and preserve kanban | existing same file | +| `backend/templates/tablos.templ` | Expand `TasksTabFragment` signature and wrapper | existing same file | +| `backend/internal/web/handlers_etapes_test.go` | Etape behavior and ownership coverage | `backend/internal/web/handlers_tasks_test.go` | +| `backend/internal/web/handlers_tasks_test.go` | Assignment/filter/reorder regression coverage | existing same file |