diff --git a/docs/INDEX.md b/docs/INDEX.md index 932620b..91a3b27 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -46,6 +46,7 @@ Auto-generated on 2026-05-14 from front-matter and headings. | [Bootstrap-wizard voor nieuwe Product-repo](./plans/M8-bootstrap-wizard-upload.md) | — | — | | [Plan v3.5 — Bootstrap-wizard voor nieuwe Product-repo (Scrum4Me feature)](./plans/M8-bootstrap-wizard.md) | reviewed | — | | [PBI-80 — Demo-gebruiker mag eigen UI-voorkeuren wijzigen](./plans/PBI-80-demo-prefs.md) | — | — | +| [Plan — `code` wordt bindende volgorde voor stories & taken; drag-and-drop eruit](./plans/PBI-84-code-binding-order.md) | — | — | | [Queue-loop verplaatsen van Claude naar runner](./plans/queue-loop-extraction.md) | — | — | | [Sprint MCP-tools — create_sprint & update_sprint](./plans/sprint-mcp-tools.md) | draft | 2026-05-11 | | [Advies - SprintRun, PR en worktree lifecycle als state machines](./plans/sprint-pr-worktree-state-machines.md) | draft | 2026-05-06 | diff --git a/docs/plans/PBI-84-code-binding-order.md b/docs/plans/PBI-84-code-binding-order.md new file mode 100644 index 0000000..6946dc8 --- /dev/null +++ b/docs/plans/PBI-84-code-binding-order.md @@ -0,0 +1,164 @@ +# Plan — `code` wordt bindende volgorde voor stories & taken; drag-and-drop eruit + +> **Status:** goedgekeurd 2026-05-14 · gematerialiseerd via Scrum4Me-MCP op de Ubuntu-DB. +> Sprint `S-2026-05-14-code-order` · **PBI-84** · Story **ST-1358** (IN_SPRINT) · Taken **T-992 t/m T-1001** (uitvoervolgorde = sort_order 1–10). +> +> _v3 — herzien na review (P0/P1/P2) + na onderzoek van het plan→onderdelen-mechanisme._ + +## Context + +Stories en taken zijn nu vrij herordenbaar via drag-and-drop (dnd-kit) in de backlog, het sprint-bord en het sprint-taakpaneel. Die drag schrijft een Float `sort_order` weg via reorder-acties. De volgorde is daardoor "los" en dubbel bepaald: `priority` (1–4) groepeert, `sort_order` sorteert daarbinnen. + +Gewenste situatie: de volgorde van stories en taken is **bindend gekoppeld aan hun `code`** (de bestaande `ST-NNN` / `T-N` identifier die al bij creatie wordt toegekend en al bijna overal zichtbaar is). Drag-and-drop verdwijnt. Het `code`-veld blijft bewerkbaar via de bestaande dialogs — dat is de bewuste, in-scope "aanpas-manier"; er komt géён vervangende herorden-UI. `priority` wordt herijkt als puur team-belang-label dat de bindende volgorde niet meer bepaalt. + +Netto: één voorspelbare volgorde = de code. De worker voert stories/taken uit in code-volgorde. + +## Beslissingen (uit grill-sessie + review) + +1. **Scope:** alleen Story + Taak. PBI's houden drag-and-drop én priority-groepering — volledig ongemoeid. +2. **Sorteersleutel:** `code` is de bindende volgorde voor stories/taken — default-weergave én worker-uitvoering. `priority` bepaalt de bindende volgorde nergens meer. **Uitzondering (P2):** in `components/backlog/story-panel.tsx` blijft een priority-sorteermodus als *niet-persistente leesweergave* (muteert niets, alleen een lens). +3. **Volgnummer = bestaande `code`.** Geen nieuw veld, geen schema-wijziging. +4. **Nummering:** product-breed met gaten (bv. `ST-001, ST-004` binnen één PBI) is prima. Geen wijziging aan code-generatie. +5. **`code` blijft bewerkbaar** via de bestaande dialog-inputs — ongemoeid. "Bindend" = volgorde volgt de code + geen drag meer. +6. **MCP in scope:** zowel de app als de externe `scrum4me-mcp`-repo worden bijgewerkt. +7. **Sprint-bord:** alleen herordenen verdwijnt (story-reorder + taak-reorder); backlog↔sprint membership-drag blijft. +8. **`priority` blijft bewerkbaar** via de edit-dialogs (label, geen bindende volgorde) — priority-afhandeling verandert verder niet. + +## Mechanisme: plan → onderdelen + +Een plan wordt op **twee manieren** omgezet in PBI + stories + taken; beide moeten voortaan code-volgorde respecteren. + +**A. Idea-materialisatie (app, UI-knop).** `actions/ideas.ts` → `materializeIdeaPlanAction` (~620-799). Leest `Idea.plan_md` — markdown met YAML-frontmatter (schema `lib/schemas/idea.ts` `ideaPlanMdFrontmatterSchema`, parser `lib/idea-plan-parser.ts` `parsePlanMd`). Frontmatter = `{ pbi, stories: [{ …, tasks: [...] }] }`; **volgorde = array-positie**, geen expliciet indexveld. De actie loopt `stories[]`/`tasks[]` in volgorde af in één transactie en kent codes toe via een inline product-brede teller (`nextStoryN++` / `nextTaskN++`). Omdat die teller monotoon meeloopt met de lus is **code-volgorde == plan-volgorde**; `sort_order = parseCodeNumber(code)` behoudt de plan-volgorde dus vanzelf. PBI-creatie hierin blijft ongemoeid (PBI buiten scope). + +**B. MCP-flow (Claude Code, ná plan-goedkeuring).** Runbook `docs/runbooks/plan-to-pbi-flow.md`: `create_sprint → create_pbi → create_story → create_task`, los aangeroepen. Elke `create_*`-tool genereert zelf de `code` (product-brede teller); volgorde = aanroep-volgorde → code-volgorde. Er is **geen** worker-job die een plan materialiseert (`IDEA_MAKE_PLAN` schrijft alleen `plan_md`). + +Beide paden zetten `sort_order` nu nog los van `code` — dat is precies wat §2 rechttrekt. + +## Aanpak + +### 1. Sorteersleutel: `sort_order` wordt numerieke spiegel van `code` + +`orderBy: { code }` kan niet (string-sortering breekt: `T-1, T-10, T-2`). De bestaande **`sort_order` Float-kolom** blijft daarom de interne numerieke sorteersleutel, maar wordt voortaan afgeleid van `code` i.p.v. via drag/membership gemuteerd. Geen schema-wijziging; `Pbi.sort_order` blijft een echte muteerbare Float. + +- Nieuwe pure helper **`parseCodeNumber(code: string): number`** in `lib/code.ts` (gedeeld, non-server): pakt de afsluitende cijferreeks (`/(\d+)$/`) en parse't naar int. Geen cijfers → grote fallback-constante (niet-conforme codes sorteren achteraan). +- Spiegel die helper in de MCP-repo (kleine bewuste duplicaat, conform `lib/job-config.ts`-patroon). +- Regel die overal geldt: **elke story/taak-create en elke `code`-edit zet `sort_order = parseCodeNumber(code)`**. Niets anders mag `Story.sort_order` / `Task.sort_order` schrijven. + +### 2. Creatie-paden — `sort_order = parseCodeNumber(code)` [P1: idea-flow + beide mechanisme-paden] + +Bereken `sort_order` ná het vaststellen van de definitieve `code`. **Eerst** `rg -n "\.(story|task)\.create" app/ actions/ scrum4me-mcp/src/` om de inventaris te bevestigen; bekende paden: + +- `actions/stories.ts` → `createStoryAction` (binnen `createWithCodeRetry`-callback). +- `actions/tasks.ts` → `saveTask` (create-tak) en `createTaskAction`. +- **`actions/ideas.ts` → `materializeIdeaPlanAction` (~723-756)** — pad A. Zet nu `code` via inline template-literal en `sort_order: si + 1` / `ti + 1`. Herstructureer: `const code = …` eerst, dan `sort_order: parseCodeNumber(code)` (= de tellerwaarde). Plan-volgorde blijft zo behouden. **PBI-creatie in deze actie niet aanraken.** +- MCP: `scrum4me-mcp/src/tools/create-story.ts` en `create-task.ts` — pad B. Vervang `(last?.sort_order ?? 0) + 1.0` door `parseCodeNumber(code)`. **Verwijder bovendien de `sort_order`-inputparameter** uit deze twee tool-schema's (anders kan een caller de code-binding omzeilen) en werk de tool-descriptions bij. **`create-pbi.ts` blijft ongemoeid** — PBI buiten scope, mag `sort_order`-input houden. + +### 3. Edit-paden — `code`-wijziging hersynchroniseert `sort_order` + +- `actions/stories.ts` → `updateStoryAction` en `actions/tasks.ts` → `saveTask` (update-tak): als `code` wijzigt, herbereken `sort_order = parseCodeNumber(nieuweCode)`. **`priority`-write blijft ongewijzigd.** +- MCP: bevestig met `rg` dat geen `update_*`-tool de `code` van een bestaande story/taak wijzigt; zo niet → geen MCP edit-werk. + +### 4. Sprint-membership ontkoppelen van `sort_order` [P0] + +Membership-acties mogen uitsluitend `sprint_id` + `status` schrijven, nooit `sort_order` — anders verliest een story z'n code-afgeleide volgorde zodra hij een sprint in/uit gaat. + +- `actions/sprints.ts` → `createSprintAction` (~regels 440-444): verwijder `sort_order: i + 1` uit de `story.update`-data. +- `actions/sprints.ts` → `addStoryToSprintAction` (~regel 541): verwijder `sort_order: (last?.sort_order ?? 0) + 1.0` (en de bijbehorende `last`-query). +- Géён wijziging nodig (raken `sort_order` al niet): `commitSprintMembershipAction`, `createSprintWithSelectionAction`, `createSprintWithPbisAction`, `removeStoryFromSprintAction`. + +### 5. Drag-and-drop & reorder verwijderen [P0: task-list.tsx toegevoegd] + +- Acties: verwijder `reorderStoriesAction` (`actions/stories.ts`), `reorderTasksAction` (`actions/tasks.ts`), `reorderSprintStoriesAction` (`actions/sprints.ts`). **Behoud** `reorderPbisAction` + `updatePbiPriorityAction` (PBI). +- REST: verwijder `app/api/stories/[id]/tasks/reorder/route.ts`. +- Backlog-UI: `components/backlog/story-panel.tsx` en `task-panel.tsx` — dnd-kit eruit (DndContext/SortableContext/handlers), platte lijst renderen. Priority-sorteermodus + priority-filter in `story-panel.tsx` blijven (zie beslissing 2). +- Sprint-bord: `components/sprint/sprint-board-client.tsx` + `sprint-backlog.tsx` — alleen de reorder-binnen-sprint-tak uit `handleDragEnd`/`handleReorder` halen; `addStoryToSprintAction`/`removeStoryFromSprintAction` (membership-drag) blijven. +- **Sprint-taakpaneel: `components/sprint/task-list.tsx`** — heeft volledige taak-reorder-DnD (`reorderTasksAction`-import, `DndContext`/`SortableContext`, `SortableTaskRow` met `useSortable`, `handleDragEnd` met `sprint-task-order` optimistic mutation). Verwijder alle DnD; `SortableTaskRow` wordt een platte rij. **Behoud** de click-based status-toggle (`handleStatusToggle` → `updateTaskStatusAction`) en de edit-actie — die zijn niet drag-gebaseerd. +- Stores: + - `stores/product-workspace/store.ts` — `story-order` en `task-order` mutatie-types/handlers eruit; **`pbi-order` blijft**. + - `stores/sprint-workspace/types.ts` — `OptimisticSprintTaskOrderMutation` (`sprint-task-order`) én `sprint-story-order` + de union-takken eruit. + - `stores/sprint-workspace/store.ts` — `rollbackMutation`-cases voor `sprint-task-order` en `sprint-story-order` eruit. +- `components/solo/solo-board.tsx`: **ongemoeid** — status-kanban (TO_DO/IN_PROGRESS/DONE), geen `sort_order`-herordening. + +### 6. Lees-queries — `priority` uit story/taak-ordering [P1: volledige inventaris] + +Story/taak-ordering wordt puur `sort_order` asc (eventueel met `created_at` als tiebreaker). **PBI-keys (`pbi.priority`, `pbi.sort_order`) blijven staan.** Begin met een `rg`-sweep, gebruik onderstaande geverifieerde lijst als minimum: + +``` +rg -n "orderBy|\.sort\(" app/ lib/ actions/ stores/ scrum4me-mcp/src/ | rg -i "priority" +``` + +**Story-ordering — `priority` laten vallen:** `app/api/products/[id]/backlog/route.ts:49` · `app/api/pbis/[id]/stories/route.ts:33` · `app/(app)/products/[id]/page.tsx:59` · `app/(mobile)/m/products/[id]/page.tsx:45` · `app/(app)/products/[id]/sprint/[sprintId]/page.tsx:131` · `app/api/products/[id]/claude-context/route.ts:61` · `app/api/products/[id]/next-story/route.ts:26` · `app/api/sprints/[id]/workspace/route.ts:47` · `actions/sprint-runs.ts:91` · `scrum4me-mcp/src/tools/get-claude-context.ts:66` · `scrum4me-mcp/src/tools/wait-for-job.ts:612` + +**Taak-ordering — `priority` laten vallen:** `app/(app)/products/[id]/page.tsx:86` · `app/(mobile)/m/products/[id]/page.tsx:72` · `app/(app)/products/[id]/sprint/[sprintId]/page.tsx:75` · `app/api/sprints/[id]/tasks/route.ts:29-32` (`story.sort_order` behouden) · `actions/sprint-runs.ts:88` + in-memory `.sort()` (~164-173: `a.priority - b.priority` voor story/taak weg, `a.pbi.*` behouden) · `lib/solo-workspace-server.ts:49-50` · `scrum4me-mcp/src/tools/get-claude-context.ts:76` · `scrum4me-mcp/src/tools/wait-for-job.ts:609` + +**Al compliant (taak al puur op `sort_order`) — niet aanraken:** `backlog/route.ts:66` · `claude-context/route.ts:64` · `next-story/route.ts:29` · `workspace/route.ts:55` + +**PBI-ordering — MOET blijven (priority behouden):** `backlog/route.ts:35` · `page.tsx:53` · `m/products/[id]/page.tsx:39` · `sprint/[sprintId]/page.tsx:128` + +**Stores:** `stores/sprint-workspace/store.ts` `compareStory`/`compareTask` — eventuele priority-tak verwijderen. + +### 7. `BacklogTask` krijgt `code` [P1] + +De directe kaart-aanpassing compileert niet zonder dit: + +- `stores/product-workspace/types.ts` (~28-37): voeg `code: string | null` toe aan `BacklogTask`. +- `app/api/stories/[id]/tasks/route.ts` (~34-43): `code: true` toevoegen aan de task-`select`. +- `app/api/products/[id]/backlog/route.ts` (~67-76): `code: true` toevoegen aan de task-`select`. +- Eventuele normalisatie die DB-task → `BacklogTask` mapt: `code` meenemen. + +### 8. Zichtbaarheid volgnummer + +Na stap 7: in `components/backlog/task-panel.tsx` `task.code` doorgeven aan `` (hergebruik bestaande `CodeBadge`). Overige views (backlog-story-kaart, sprint-rijen, sprint-taaklijst, solo-kaart, dialogs) tonen de code al. + +### 9. Backfill bestaande data + +Eenmalige Prisma-migratie (raw SQL): zet `stories.sort_order` en `tasks.sort_order` = numerieke staart van `code` (`CAST(SUBSTRING(code FROM '[0-9]+$') AS DOUBLE PRECISION)`, `COALESCE` voor niet-numerieke codes). Gevolg: bestaande stories/taken verspringen eenmalig naar code-volgorde — de bedoelde uitkomst. Niet draaien terwijl een PER_TASK-run actief is (SPRINT_BATCH is snapshot-beschermd via `SprintTaskExecution.order`). + +### 10. Docs & tests + +- `docs/patterns/sort-order.md` herschrijven: float-insertion geldt nog uitsluitend voor PBI; story/taak-`sort_order` is een afgeleide numerieke spiegel van `code`, nooit handmatig/membership-gemuteerd. +- **`docs/runbooks/plan-to-pbi-flow.md` herschrijven:** story/taak-volgorde = code-volgorde (= aanroep-volgorde), niet "priority dan call-order". +- **`lib/idea-prompts/make-plan.md` controleren:** verwijder/verbeter eventuele uitspraken die suggereren dat `priority` de uitvoervolgorde bepaalt (priority = label; array-volgorde = volgorde). +- `docs/api/rest-contract.md`: ordering-omschrijving voor stories/taken bijwerken (code-volgorde i.p.v. `[priority, sort_order]`). +- Nieuwe ADR in `docs/adr/`: "`code` is de bindende volgordesleutel voor stories/taken; priority is label". +- Tests: `__tests__/api/reorder.test.ts` verwijderen; `__tests__/actions/ideas-crud.test.ts` (~478-576, materialisatie) bijwerken; backlog-component-tests + `product-workspace`- en `sprint-workspace`-store-tests bijwerken (drag/`*-order`-mutaties weg); `sprint-tasks`/`next-story`/`sprint-runs`-tests: orderBy-verwachtingen bijwerken. Nieuw: `parseCodeNumber`-unit; create zet `sort_order = numeriek(code)` (incl. idea-materialisatie); code-edit hersynchroniseert; membership-actie laat `sort_order` ongemoeid. + +## Kritieke bestanden + +**App — wijzigen:** `lib/code.ts` (+`parseCodeNumber`), `actions/stories.ts`, `actions/tasks.ts`, `actions/sprints.ts`, `actions/ideas.ts`, `actions/sprint-runs.ts`, `lib/solo-workspace-server.ts`, `app/api/products/[id]/backlog/route.ts`, `app/api/pbis/[id]/stories/route.ts`, `app/api/products/[id]/claude-context/route.ts`, `app/api/products/[id]/next-story/route.ts`, `app/api/sprints/[id]/tasks/route.ts`, `app/api/sprints/[id]/workspace/route.ts`, `app/api/stories/[id]/tasks/route.ts`, `app/(app)/products/[id]/page.tsx`, `app/(mobile)/m/products/[id]/page.tsx`, `app/(app)/products/[id]/sprint/[sprintId]/page.tsx`, `components/backlog/story-panel.tsx`, `components/backlog/task-panel.tsx`, `components/sprint/sprint-board-client.tsx`, `components/sprint/sprint-backlog.tsx`, `components/sprint/task-list.tsx`, `stores/product-workspace/types.ts`, `stores/product-workspace/store.ts`, `stores/sprint-workspace/types.ts`, `stores/sprint-workspace/store.ts`. +**App — verwijderen:** `app/api/stories/[id]/tasks/reorder/route.ts`. +**MCP:** `scrum4me-mcp/src/tools/create-story.ts`, `create-task.ts` (sort_order-logica + input-param weg), `get-claude-context.ts`, `wait-for-job.ts` (+ kleine `parseCodeNumber`-duplicaat). **Niet:** `create-pbi.ts`. +**Hergebruiken:** `nextSequential`/`generateNext*Code` (`lib/code-server.ts`), `createWithCodeRetry`, `parsePlanMd` (`lib/idea-plan-parser.ts`), `CodeBadge`, `BacklogCard`. + +## Verificatie + +1. `npm run verify && npm run build` (lint + typecheck + test) — in app én MCP-repo. +2. Handmatig (dev-server): nieuwe story/taak (auto-code) landt op code-positie; `code` editen → herordent; `priority` editen → kleur wijzigt, bindende volgorde níét; priority-sorteermodus in story-panel werkt nog als tijdelijke lens; slepen is weg in backlog, sprint-bord én sprint-taaklijst; story toevoegen/verwijderen aan sprint via drag werkt nog en behoudt code-volgorde. +3. Worker-pad: `GET /api/products/:id/next-story` en `GET /api/sprints/:id/tasks` geven code-volgorde terug; MCP `get_claude_context` / `wait_for_job` idem. +4. Creatie: story/taak via app-actie, idea-materialisatie (`materializeIdeaPlanAction` — plan-volgorde behouden) én MCP → `sort_order` = numeriek(code). +5. Membership: story aan sprint toevoegen/verwijderen → `sort_order` ongewijzigd. +6. Backfill-migratie draaien → bestaande stories/taken in code-volgorde. + +## Open implementatie-details (niet-blokkerend) + +- `parseCodeNumber`: niet-numerieke code → grote fallback-constante; `created_at`/`id` als tiebreaker bij gelijke numerieke waarde. +- `app/api/sprints/[id]/tasks/route.ts` toont nu een afgeleide `${story.code}.${positie}` — optioneel vervangen door echte `task.code` (nu beschikbaar). +- `sort_order` blijft `Float` en behoudt zijn naam (Float→Int of hernoemen = extra migratie + MCP schema-resync; niet de moeite — wel duidelijk documenteren dat het veld voor story/taak een afgeleide is). +- Idea-materialisatie houdt zijn eigen inline code-teller (niet consolideren met `lib/code-server.ts` — buiten scope). + +## Taken-mapping (product Scrum4Me, Ubuntu-DB) + +| Taak | sort_order | Plan-§ | Repo | +|---|---|---|---| +| T-992 — `parseCodeNumber`-helper | 1 | §1 | app | +| T-993 — `code` op `BacklogTask`-type + queries | 2 | §7 | app | +| T-994 — create + edit: `sort_order` afgeleid van code | 3 | §2, §3 | app | +| T-995 — sprint-membership ontkoppelen van `sort_order` | 4 | §4 | app | +| T-996 — `priority` uit story/taak-orderings | 5 | §6 | app | +| T-997 — drag-and-drop & reorder verwijderen | 6 | §5 | app | +| T-998 — `code` op backlog-taakkaarten | 7 | §8 | app | +| T-999 — scrum4me-mcp: code-volgorde + priority eruit | 8 | §2, §6 | scrum4me-mcp | +| T-1000 — backfill-migratie | 9 | §9 | app | +| T-1001 — docs & tests | 10 | §10 | app | + +> De MCP-`create_story` `sprint_id`-koppeling (de "gap" uit de planfase) is **al** geïmplementeerd buiten deze story om, samen met de DB-pointer-fix (`~/.claude.json` → Ubuntu). Taak T-999 mag die wijziging niet terugdraaien.