# 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 `<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.
