--- title: "sort_order — PBI drag-and-drop vs. code-bindende volgorde voor stories/taken" status: active audience: [ai-agent, contributor] language: nl last_updated: 2026-05-14 when_to_read: "When implementing ordering for PBIs (drag-and-drop) or stories/tasks (code-binding)." --- # Patroon: sort_order — PBI vs. Story/Taak `sort_order` heeft voor PBI's een andere betekenis dan voor stories en taken. --- ## PBI — float-insertion (drag-and-drop) PBI's ondersteunen drag-and-drop herordening. `sort_order` is een `Float` die via de midpoint-formule wordt berekend bij tussenvoeging: ```ts function getSortOrder(before: number | null, after: number | null): number { if (before === null && after === null) return 1.0 if (before === null) return after! / 2 if (after === null) return before + 1.0 return (before + after) / 2 } ``` ### Herindexeer als precisie opraakt Trigger wanneer het kleinste verschil tussen twee opeenvolgende PBI's < 0.001 is: ```ts async function reindexIfNeeded(items: { id: string; sort_order: number }[]) { const minGap = Math.min(...items.slice(1).map((item, i) => item.sort_order - items[i].sort_order )) if (minGap < 0.001) { await Promise.all(items.map((item, i) => prisma.pbi.update({ where: { id: item.id }, data: { sort_order: i + 1.0 } }) )) } } ``` ### Reorder Server Action (PBI-only) Een drag-and-drop reorder stuurt client-controlled ID-lijsten naar de server. Behandel die lijst als onbetrouwbaar: - Weiger dubbele IDs. - Haal alle IDs op met de parent-scope (`product_id`). - Weiger de operatie als het aantal gevonden records niet exact gelijk is aan het aantal aangeleverde IDs. - Update pas daarna `sort_order` in een transactie. - Gebruik bij priority changes dezelfde parent uit de database, niet een los meegegeven `productId`. --- ## Story / Taak — code-bindende volgorde (geen drag-and-drop) Voor stories en taken is `sort_order` een **numerieke spiegel van `code`**, berekend via `parseCodeNumber(code)` uit `lib/code.ts`. Drag-and-drop herordening bestaat niet voor stories en taken. ### Wanneer `sort_order` wordt gezet | Moment | Wat er gebeurt | |---|---| | `story.create` / `task.create` | `sort_order = parseCodeNumber(code)` | | Idea-materialisatie (`materializeIdeaPlanAction`) | idem — stories en taken krijgen `sort_order = parseCodeNumber(storyCode / taskCode)` | | Code-edit (PATCH met nieuw `code`) | `sort_order = parseCodeNumber(newCode)` wordt bijgewerkt | | Sprint-membership-acties | `sort_order` wordt **niet** aangeraakt | ### `parseCodeNumber` Extraheert het trailertal uit een code-string: ```ts // lib/code.ts export function parseCodeNumber(code: string): number { const match = code.match(/(\d+)$/) return match ? parseInt(match[1], 10) : 0 } ``` Voorbeelden: `"ST-042"` → `42`, `"T-7"` → `7`, `"CUSTOM-FOO"` → `0`. ### Ordering queries Stories en taken worden **uitsluitend** op `sort_order` geordend — nooit op `priority`: ```ts // stories binnen een sprint orderBy: [{ sort_order: 'asc' }] // taken binnen een story orderBy: { sort_order: 'asc' } // taken binnen een sprint (story-volgorde eerst) orderBy: [{ story: { sort_order: 'asc' } }, { sort_order: 'asc' }] ``` `priority` is een **label** (urgentie-aanduiding voor de gebruiker), geen sorteerkriteria voor stories of taken.