docs(09): create phase plan

This commit is contained in:
Arthur Belleville 2026-05-15 22:30:18 +02:00
parent 29691e82a3
commit 0e6347ef26
No known key found for this signature in database
6 changed files with 626 additions and 9 deletions

View file

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

View file

@ -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"
---
<objective>
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.
</objective>
<context>
@/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
</context>
<threat_model>
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.
</threat_model>
<tasks>
<task type="auto">
<name>Task 1: Add RED coverage for first etape slice</name>
<files>
- backend/internal/web/handlers_etapes_test.go
- backend/internal/web/handlers_tasks_test.go
</files>
<read_first>
- 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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && go test ./internal/web -run 'TestEtapeCreateRendersChipAndCount|TestTaskCreateAssignsEtape|TestEtapeFilterRendersExistingKanbanColumns' -count=1</automated>
</verify>
<acceptance_criteria>
- `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.
</acceptance_criteria>
</task>
<task type="auto">
<name>Task 2: Implement the thinnest create-filter-assigned-task slice</name>
<files>
- 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
</files>
<read_first>
- 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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && just generate && go test ./internal/web -run 'TestEtapeCreateRendersChipAndCount|TestTaskCreateAssignsEtape|TestEtapeFilterRendersExistingKanbanColumns|TestTaskCreate' -count=1</automated>
</verify>
<acceptance_criteria>
- `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.
</acceptance_criteria>
</task>
</tasks>
<verification>
Run:
- `cd backend && just generate`
- `cd backend && go test ./internal/web -count=1`
- `git diff --check`
</verification>
<success_criteria>
- 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.
</success_criteria>

View file

@ -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"
---
<objective>
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.
</objective>
<context>
@/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
</context>
<threat_model>
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.
</threat_model>
<tasks>
<task type="auto">
<name>Task 1: Add RED coverage for etape management</name>
<files>
- backend/internal/web/handlers_etapes_test.go
</files>
<read_first>
- 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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && go test ./internal/web -run 'TestEtapeUpdateChangesTitleAndDescription|TestEtapeDeleteUnassignsTasks|TestEtapeReorderPersistsPosition|TestEtapeOwnershipReturns404' -count=1</automated>
</verify>
<acceptance_criteria>
- `handlers_etapes_test.go` contains all four named tests.
- Tests fail before implementation because update/delete/reorder routes or behavior are missing.
</acceptance_criteria>
</task>
<task type="auto">
<name>Task 2: Implement edit, delete-unassign, and reorder</name>
<files>
- 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
</files>
<read_first>
- 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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && just generate && go test ./internal/web -run 'TestEtapeUpdateChangesTitleAndDescription|TestEtapeDeleteUnassignsTasks|TestEtapeReorderPersistsPosition|TestEtapeOwnershipReturns404' -count=1</automated>
</verify>
<acceptance_criteria>
- `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.
</acceptance_criteria>
</task>
</tasks>
<verification>
Run:
- `cd backend && just generate`
- `cd backend && go test ./internal/web -count=1`
- `git diff --check`
</verification>
<success_criteria>
- 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.
</success_criteria>

View file

@ -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"
---
<objective>
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.
</objective>
<context>
@/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
</context>
<threat_model>
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.
</threat_model>
<tasks>
<task type="auto">
<name>Task 1: Add RED coverage for assignment and filters</name>
<files>
- backend/internal/web/handlers_tasks_test.go
</files>
<read_first>
- 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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && go test ./internal/web -run 'TestTaskEditRendersEtapeSelector|TestTaskUpdateAssignsAndUnassignsEtape|TestTaskAssignmentRejectsForeignEtape|TestTasksTabUnassignedFilter' -count=1</automated>
</verify>
<acceptance_criteria>
- `handlers_tasks_test.go` contains all four named tests.
- Tests fail before implementation because selector/update/filter behavior is incomplete.
</acceptance_criteria>
</task>
<task type="auto">
<name>Task 2: Implement form assignment and filter-aware task loading</name>
<files>
- 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
</files>
<read_first>
- 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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && just generate && go test ./internal/web -run 'TestTaskEditRendersEtapeSelector|TestTaskUpdateAssignsAndUnassignsEtape|TestTaskAssignmentRejectsForeignEtape|TestTasksTabUnassignedFilter|TestTaskUpdate|TestTasksKanbanRenders' -count=1</automated>
</verify>
<acceptance_criteria>
- `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.
</acceptance_criteria>
</task>
</tasks>
<verification>
Run:
- `cd backend && just generate`
- `cd backend && go test ./internal/web -count=1`
- `git diff --check`
</verification>
<success_criteria>
- ETAPE-03 is fully delivered.
- ETAPE-05 filtering behavior is delivered for All, Unassigned, and specific etapes.
- Existing task create/update tests still pass.
</success_criteria>

View file

@ -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"
---
<objective>
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.
</objective>
<context>
@/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
</context>
<threat_model>
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.
</threat_model>
<tasks>
<task type="auto">
<name>Task 1: Add reorder preservation regression tests</name>
<files>
- backend/internal/web/handlers_tasks_test.go
</files>
<read_first>
- backend/internal/web/handlers_tasks_test.go
- backend/internal/web/handlers_tasks.go
- .planning/phases/09-etapes/09-RESEARCH.md
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && go test ./internal/web -run 'TestTaskReorderPreservesEtapeAssignment|TestFilteredTaskReorderPreservesHiddenTasks|TestTaskOrderPersistsWithEtapeFilter' -count=1</automated>
</verify>
<acceptance_criteria>
- `handlers_tasks_test.go` contains all three named regression tests.
- Tests fail before implementation if reorder does not preserve `etape_id`.
</acceptance_criteria>
</task>
<task type="auto">
<name>Task 2: Preserve etape assignment through reorder and refresh counts</name>
<files>
- backend/internal/web/handlers_tasks.go
- backend/templates/tasks.templ
- backend/templates/etapes.templ
</files>
<read_first>
- backend/internal/web/handlers_tasks.go
- backend/templates/tasks.templ
- backend/templates/etapes.templ
- .planning/phases/09-etapes/09-RESEARCH.md
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && just generate && go test ./internal/web -run 'TestTaskReorderPreservesEtapeAssignment|TestFilteredTaskReorderPreservesHiddenTasks|TestTaskOrderPersistsWithEtapeFilter|TestTaskReorderCrossColumn|TestTaskReorderSameColumn|TestTaskOrderPersists' -count=1</automated>
</verify>
<acceptance_criteria>
- 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.
</acceptance_criteria>
</task>
<task type="manual">
<name>Task 3: Final automated and browser verification checkpoint</name>
<files></files>
<read_first>
- .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
</read_first>
<action>
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.
</action>
<verify>
<automated>cd backend && just generate && go test ./... -count=1</automated>
<manual>Browser confirms all ETAPE-01..06 success criteria and UI-SPEC interaction contract.</manual>
</verify>
<acceptance_criteria>
- `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.
</acceptance_criteria>
</task>
</tasks>
<verification>
Run:
- `cd backend && just generate`
- `cd backend && go test ./... -count=1`
- `git diff --check`
- browser verification from Task 3
</verification>
<success_criteria>
- 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.
</success_criteria>

View file

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