Goedgekeurd plan voor PBI-84: code wordt de bindende sorteersleutel voor stories/taken, drag-and-drop herordening verdwijnt. Herzien na multi-model review (P0/P1/P2) + onderzoek van het plan->onderdelen-mechanisme. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
17 KiB
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)
- Scope: alleen Story + Taak. PBI's houden drag-and-drop én priority-groepering — volledig ongemoeid.
- Sorteersleutel:
codeis de bindende volgorde voor stories/taken — default-weergave én worker-uitvoering.prioritybepaalt de bindende volgorde nergens meer. Uitzondering (P2): incomponents/backlog/story-panel.tsxblijft een priority-sorteermodus als niet-persistente leesweergave (muteert niets, alleen een lens). - Volgnummer = bestaande
code. Geen nieuw veld, geen schema-wijziging. - Nummering: product-breed met gaten (bv.
ST-001, ST-004binnen één PBI) is prima. Geen wijziging aan code-generatie. codeblijft bewerkbaar via de bestaande dialog-inputs — ongemoeid. "Bindend" = volgorde volgt de code + geen drag meer.- MCP in scope: zowel de app als de externe
scrum4me-mcp-repo worden bijgewerkt. - Sprint-bord: alleen herordenen verdwijnt (story-reorder + taak-reorder); backlog↔sprint membership-drag blijft.
priorityblijft 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): numberinlib/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 zetsort_order = parseCodeNumber(code). Niets anders magStory.sort_order/Task.sort_orderschrijven.
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(binnencreateWithCodeRetry-callback).actions/tasks.ts→saveTask(create-tak) encreateTaskAction.actions/ideas.ts→materializeIdeaPlanAction(~723-756) — pad A. Zet nucodevia inline template-literal ensort_order: si + 1/ti + 1. Herstructureer:const code = …eerst, dansort_order: parseCodeNumber(code)(= de tellerwaarde). Plan-volgorde blijft zo behouden. PBI-creatie in deze actie niet aanraken.- MCP:
scrum4me-mcp/src/tools/create-story.tsencreate-task.ts— pad B. Vervang(last?.sort_order ?? 0) + 1.0doorparseCodeNumber(code). Verwijder bovendien desort_order-inputparameter uit deze twee tool-schema's (anders kan een caller de code-binding omzeilen) en werk de tool-descriptions bij.create-pbi.tsblijft ongemoeid — PBI buiten scope, magsort_order-input houden.
3. Edit-paden — code-wijziging hersynchroniseert sort_order
actions/stories.ts→updateStoryActionenactions/tasks.ts→saveTask(update-tak): alscodewijzigt, herberekensort_order = parseCodeNumber(nieuweCode).priority-write blijft ongewijzigd.- MCP: bevestig met
rgdat geenupdate_*-tool decodevan 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): verwijdersort_order: i + 1uit destory.update-data.actions/sprints.ts→addStoryToSprintAction(~regel 541): verwijdersort_order: (last?.sort_order ?? 0) + 1.0(en de bijbehorendelast-query).- Géён wijziging nodig (raken
sort_orderal 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). BehoudreorderPbisAction+updatePbiPriorityAction(PBI). - REST: verwijder
app/api/stories/[id]/tasks/reorder/route.ts. - Backlog-UI:
components/backlog/story-panel.tsxentask-panel.tsx— dnd-kit eruit (DndContext/SortableContext/handlers), platte lijst renderen. Priority-sorteermodus + priority-filter instory-panel.tsxblijven (zie beslissing 2). - Sprint-bord:
components/sprint/sprint-board-client.tsx+sprint-backlog.tsx— alleen de reorder-binnen-sprint-tak uithandleDragEnd/handleReorderhalen;addStoryToSprintAction/removeStoryFromSprintAction(membership-drag) blijven. - Sprint-taakpaneel:
components/sprint/task-list.tsx— heeft volledige taak-reorder-DnD (reorderTasksAction-import,DndContext/SortableContext,SortableTaskRowmetuseSortable,handleDragEndmetsprint-task-orderoptimistic mutation). Verwijder alle DnD;SortableTaskRowwordt 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-orderentask-ordermutatie-types/handlers eruit;pbi-orderblijft.stores/sprint-workspace/types.ts—OptimisticSprintTaskOrderMutation(sprint-task-order) énsprint-story-order+ de union-takken eruit.stores/sprint-workspace/store.ts—rollbackMutation-cases voorsprint-task-orderensprint-story-ordereruit.
components/solo/solo-board.tsx: ongemoeid — status-kanban (TO_DO/IN_PROGRESS/DONE), geensort_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): voegcode: string | nulltoe aanBacklogTask.app/api/stories/[id]/tasks/route.ts(~34-43):code: truetoevoegen aan de task-select.app/api/products/[id]/backlog/route.ts(~67-76):code: truetoevoegen aan de task-select.- Eventuele normalisatie die DB-task →
BacklogTaskmapt:codemeenemen.
8. Zichtbaarheid volgnummer
Na stap 7: in components/backlog/task-panel.tsx task.code doorgeven aan <BacklogCard code={...}> (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.mdherschrijven: float-insertion geldt nog uitsluitend voor PBI; story/taak-sort_orderis een afgeleide numerieke spiegel vancode, nooit handmatig/membership-gemuteerd.docs/runbooks/plan-to-pbi-flow.mdherschrijven: story/taak-volgorde = code-volgorde (= aanroep-volgorde), niet "priority dan call-order".lib/idea-prompts/make-plan.mdcontroleren: verwijder/verbeter eventuele uitspraken die suggereren datpriorityde 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/: "codeis de bindende volgordesleutel voor stories/taken; priority is label". - Tests:
__tests__/api/reorder.test.tsverwijderen;__tests__/actions/ideas-crud.test.ts(~478-576, materialisatie) bijwerken; backlog-component-tests +product-workspace- ensprint-workspace-store-tests bijwerken (drag/*-order-mutaties weg);sprint-tasks/next-story/sprint-runs-tests: orderBy-verwachtingen bijwerken. Nieuw:parseCodeNumber-unit; create zetsort_order = numeriek(code)(incl. idea-materialisatie); code-edit hersynchroniseert; membership-actie laatsort_orderongemoeid.
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
npm run verify && npm run build(lint + typecheck + test) — in app én MCP-repo.- Handmatig (dev-server): nieuwe story/taak (auto-code) landt op code-positie;
codeediten → herordent;priorityediten → 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. - Worker-pad:
GET /api/products/:id/next-storyenGET /api/sprints/:id/tasksgeven code-volgorde terug; MCPget_claude_context/wait_for_jobidem. - Creatie: story/taak via app-actie, idea-materialisatie (
materializeIdeaPlanAction— plan-volgorde behouden) én MCP →sort_order= numeriek(code). - Membership: story aan sprint toevoegen/verwijderen →
sort_orderongewijzigd. - Backfill-migratie draaien → bestaande stories/taken in code-volgorde.
Open implementatie-details (niet-blokkerend)
parseCodeNumber: niet-numerieke code → grote fallback-constante;created_at/idals tiebreaker bij gelijke numerieke waarde.app/api/sprints/[id]/tasks/route.tstoont nu een afgeleide${story.code}.${positie}— optioneel vervangen door echtetask.code(nu beschikbaar).sort_orderblijftFloaten 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_storysprint_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.