Scrum4Me/docs/old/plans/PBI-75-sprint-task-edit-store.md
Janpeter Visser b39c3ec2e1
docs(cleanup): archief verouderde plannen, backlog en root-duplicaten (#191)
* docs(cleanup): archief verouderde plannen, backlog en root-duplicaten

- 6 plans naar docs/old/plans/ (PBI-11/75/78, user-settings-store, Local github setup, lees-de-readme — laatste was verkeerde repo)
- docs/backlog/ naar docs/old/backlog/ (pre-MCP statische registry; live werk loopt via Scrum4Me-MCP)
- 6 root-level duplicaten naar docs/old/ (functional, {pbi,story,task}-dialog, product-backlog, backlog)
- 2 landing plans (niet uitgevoerd) krijgen archived: true frontmatter — blijven op plek maar uit INDEX
- scripts/generate-docs-index.mjs: skip docs/old/** + skip archived: true
- CLAUDE.md: rijen docs/backlog/, docs/plans/<key>-*.md, docs/manual/ weg; Track B-sectie verwijderd
- README.md / CHANGELOG.md / docs/plans/v1-readiness.md: link-fixes naar nieuwe locaties

Verify groen (lint + typecheck + 718 tests). docs/INDEX.md geregenereerd.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cleanup): registreer handmatige verplaatsingen en fix referenties

- 4 plans verplaatst naar docs/old/plans/ (M10-qr-pairing-login, auto-pr-deploy-sync, docs-restructure-ai-lookup, v1-readiness)
- 3 archive-plans verplaatst naar docs/old/plans/ (archive-map nu leeg)
- ST-1114-copilot-reviews + 3 research-docs naar nieuwe docs/Ideas/ map
- Duplicaat docs/old/2026-04-27-m8-realtime-solo.md verwijderd (origineel zit in docs/old/plans/)
- Link-fixes naar nieuwe locaties:
  - CHANGELOG.md → docs/old/plans/v1-readiness.md
  - docs/runbooks/deploy-control.md → docs/old/plans/auto-pr-deploy-sync.md (2x)
  - docs/runbooks/worker-idempotency.md → docs/old/plans/auto-pr-deploy-sync.md
  - docs/plans/docs-restructure-pbi-spec.md → docs/old/plans/docs-restructure-ai-lookup.md (4x text + 2x href)
- docs/INDEX.md geregenereerd (96 docs, was 100)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 19:46:00 +02:00

7.2 KiB

PBI-75 — Sprint task-edit client-side via workspace-store

Context

In het Sprint-scherm (/products/<id>/sprint/<sprintId>) duurt het bewerken van een taak onevenredig lang. Klik op een taakregel of het potlood-icoon roept router.push(?editTask=<id>) aan vanuit components/sprint/task-list.tsx:226. Dat triggert:

  • Volledige server re-render van app/(app)/products/[id]/sprint/[sprintId]/page.tsx met zware queries (sprint, alle sprintStories+tasks, productMembers, alle PBIs+stories voor het backlog-paneel)
  • Tweede Prisma-query in EditTaskLoader voor task-detail (incl. implementation_plan)
  • Na save: router.push(closePath) + revalidatePath → opnieuw alle queries

De sprint-workspace-store (sinds PBI-74 Story 9) bevat al alles voor een client-side edit-flow:

  • setActiveTask(taskId) (regel 337) — zet context.activeTaskId + roept ensureTaskLoaded() aan
  • ensureTaskLoaded(taskId) (regel 414) — GET /api/tasks/{id} → upsert met _detail: true
  • selectActiveTask (regel 91) — bestaat, nog geen consumer
  • applyTaskEvent (regel 748) — SSE-events propageren idempotent na server-save
  • Optimistic-mutation primitives (applyOptimisticMutation / settleMutation / rollbackMutation)

Het patroon "URL-param → store" bestaat al voor product-workspace in components/backlog/url-task-sync.tsx — we volgen dat als precedent.

Doel: klik op een taak opent de edit-dialoog client-side via store-state binnen ~100ms. Geen URL-navigatie, geen server re-render, alleen GET /api/tasks/{id} voor het detail.

Aanpak

Architectuur: store-mounted dialog + URL-sync component voor deeplinks.

  1. Klik-flow: TaskList.openEditDialog roept setActiveTask(taskId) aan op de store. Geen router.push.
  2. Render-flow: nieuwe client-component SprintTaskDialogMount zit binnen SprintHydrationWrapper, subscribet selectActiveTask, en rendert <TaskDialog> zodra de active task _detail === true is.
  3. Save-flow: TaskDialog krijgt optionele onClose/onSaved callbacks (backwards compatible met bestaande closePath). Mount geeft onClose = () => setActiveTask(null). Server action saveTask blijft revalidatePath doen voor server-cache; SSE-event update store via applyTaskEvent.
  4. Deeplink-flow: nieuwe SprintUrlTaskSync leest ?editTask=<id> en roept setActiveTask(id) aan (analoog aan url-task-sync.tsx).

Bestanden + wijzigingen

Nieuw — components/sprint/sprint-task-dialog-mount.tsx

Client component. Subscribet selectActiveTask (single-value, geen useShallow). Wanneer task aanwezig is en isDetail(task) true, mappt naar TaskDialogTask-shape:

  • status: via taskStatusFromApi uit lib/task-status.ts (lowercase API → Prisma UPPER_SNAKE)
  • implementation_plan: task.implementation_plan ?? null
  • created_at: new Date(task.created_at)

Rendert <TaskDialog task={mapped} productId={productId} onClose={() => setActiveTask(null)} isDemo={isDemo} />. Geen render tussen setActiveTask en _detail: true (detail-fetch <100ms).

Nieuw — components/sprint/sprint-url-task-sync.tsx

Kopie van components/backlog/url-task-sync.tsx maar tegen useSprintWorkspaceStore en writeTaskHint uit stores/sprint-workspace/restore.

Wijziging — components/sprint/task-list.tsx (regels 225-227)

Vervang:

function openEditDialog(taskId: string) {
  router.push(`${pathname}?editTask=${taskId}`)
}

door:

function openEditDialog(taskId: string) {
  useSprintWorkspaceStore.getState().setActiveTask(taskId)
}

openCreateDialog (regel 222) blijft URL-gebaseerd — out-of-scope.

Wijziging — app/(app)/products/[id]/sprint/[sprintId]/page.tsx

  • Verwijder editTask uit searchParams-destructuring (regel 36)
  • Verwijder editTask &&-block met <Suspense><EditTaskLoader> (regels 250-260)
  • Verwijder ongebruikte imports (EditTaskLoader, TaskDialogSkeleton, evt. Suspense)
  • Mount binnen SprintHydrationWrapper:
    <SprintHydrationWrapper ...>
      <SprintBoardClient ... />
      <SprintTaskDialogMount productId={id} isDemo={isDemo} />
      <SprintUrlTaskSync />
    </SprintHydrationWrapper>
    
  • newTask-block (regels 241-248) blijft ongemoeid — out-of-scope.

Wijziging — app/_components/tasks/task-dialog.tsx

Maak closePath optioneel + voeg onClose/onSaved toe (backwards compatible):

interface TaskDialogProps {
  task?: TaskDialogTask
  storyId?: string
  productId: string
  closePath?: string
  onClose?: () => void
  onSaved?: (taskId: string) => void
  isDemo?: boolean
}

Refactor de drie router.push(closePath)-calls (regels 104, 120, 155) naar één helper:

function close() {
  if (onClose) { onClose(); return }
  if (closePath) router.push(closePath)
}

Bestaande callers (EditTaskLoader, mobile, product-page, sprint newTask-block) blijven werken via closePath. Nieuwe SprintTaskDialogMount gebruikt onClose.

Geen wijziging

  • stores/sprint-workspace/selectors.tsselectActiveTask bestaat al
  • app/_components/tasks/edit-task-loader.tsx — nog gebruikt door product-page en mobile

Edge cases

  • Status-enum mapping: store API-lowercase → Prisma UPPER_SNAKE via taskStatusFromApi, fallback 'TO_DO'
  • _detail: true race: mount rendert pas wanneer isDetail(task) true is — geen flash met undefined velden
  • Demo-mode: prop blijft via server doorlopen, dialog respecteert al isDemo
  • Dirty-close-guard: ingebouwd in dialog (regels 107, 172) — werkt via onClose
  • SSE na save: applyTaskEvent updatet store automatisch
  • Deeplink + task niet bestaat: GET /api/tasks/{id} 404 → store doet niets, dialog opent niet (huidige redirect() verdwijnt — acceptabel)

Verificatie

  1. Browser (npm run dev): klik op task in takenlijst → dialog opent <100ms, geen URL-verandering, alleen GET /api/tasks/<id> in Network
  2. Save: wijzig titel → Opslaan → dialog sluit → store toont nieuwe titel via SSE
  3. Deeplink: ?editTask=<id> → dialog opent via SprintUrlTaskSync
  4. Bestaande flows ongebroken: product-page edit, mobile edit, sprint ?newTask=1
  5. npm run verify && npm run build
  6. Vitest: __tests__/components/sprint/sprint-task-dialog-mount.test.tsx — hydreer store, mock fetch, setActiveTask(id), assert UPPER_SNAKE status + onClose clear

Risico's

  • Andere mounts (mobile, product-backlog, sprint newTask) blijven URL-gebaseerd — closePath? optional houdt ze werkend
  • Geen redirect() bij not-found-deeplink (klein UX-verschil)
  • SSE-latency 100-500ms na save — eventueel later mitigeren via applyOptimisticMutation in onSaved-callback

Out-of-scope (follow-up PBIs)

  • ?newTask=1-flow naar store
  • Mobile + product-backlog mounts
  • EditTaskLoader verwijderen wanneer alle callers over zijn