Scrum4Me/docs/plans/PBI-84-code-binding-order.md
Janpeter Visser 1de872298d
docs(plans): PBI-84 plan — code als bindende volgorde voor stories & taken (#202)
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>
2026-05-14 16:30:53 +02:00

17 KiB
Raw Blame History

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 110).

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 (14) 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.tsmaterializeIdeaPlanAction (~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.tscreateStoryAction (binnen createWithCodeRetry-callback).
  • actions/tasks.tssaveTask (create-tak) en createTaskAction.
  • actions/ideas.tsmaterializeIdeaPlanAction (~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.tsupdateStoryAction en actions/tasks.tssaveTask (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.tscreateSprintAction (~regels 440-444): verwijder sort_order: i + 1 uit de story.update-data.
  • actions/sprints.tsaddStoryToSprintAction (~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 (handleStatusToggleupdateTaskStatusAction) en de edit-actie — die zijn niet drag-gebaseerd.
  • Stores:
    • stores/product-workspace/store.tsstory-order en task-order mutatie-types/handlers eruit; pbi-order blijft.
    • stores/sprint-workspace/types.tsOptimisticSprintTaskOrderMutation (sprint-task-order) én sprint-story-order + de union-takken eruit.
    • stores/sprint-workspace/store.tsrollbackMutation-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 <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.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.