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