Bij sprint-switch wordt de sprint-content server-side opgevraagd. Wanneer
de sprint precies één PBI (en die PBI exact één story binnen de sprint)
heeft, worden PBI en story automatisch geselecteerd. Alle drie keuzes
(sprint, pbi, story) worden atomair in user-settings opgeslagen zodat ze
cross-device blijven hangen.
- lib/user-settings.ts: layout krijgt nullable activePbis +
activeStories per product.
- lib/active-sprint.ts: setActiveSelectionInSettings schrijft de drie
keys atomair + notify pg_notify.
- actions/active-sprint.ts: switchActiveSprintAction(productId, sprintId)
doet de server-side auto-select-resolutie (single PBI → single story)
en returnt { sprintId, pbiId, storyId }.
- components/shared/sprint-switcher.tsx: handleSwitchSprint roept de
nieuwe action aan en synchroniseert de workspace-store gelijk zodat
de UI geen flash krijgt voor de SSR-refresh.
- components/backlog/active-selection-hydrator.tsx (nieuw): client-side
effect dat user-settings.activePbis/activeStories naar workspace-store
spiegelt; wint van de localStorage hint-restore.
- app/(app)/products/[id]/page.tsx: ActiveSelectionHydrator gemount
binnen BacklogHydrationWrapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 lines
2 KiB
TypeScript
53 lines
2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect } from 'react'
|
|
import { useUserSettingsStore } from '@/stores/user-settings/store'
|
|
import { useProductWorkspaceStore } from '@/stores/product-workspace/store'
|
|
|
|
interface ActiveSelectionHydratorProps {
|
|
productId: string
|
|
}
|
|
|
|
/**
|
|
* PBI-79: hydrateert de workspace-store met de actieve PBI/story die in
|
|
* user-settings staan opgeslagen. Loopt na elke (re)hydratatie en bij
|
|
* mutaties van de user-settings (bv. na sprint-switch). Wint van de
|
|
* localStorage hint-restore — user-settings is de cross-device source of
|
|
* truth.
|
|
*/
|
|
export function ActiveSelectionHydrator({ productId }: ActiveSelectionHydratorProps) {
|
|
const hydrated = useUserSettingsStore((s) => s.context.hydrated)
|
|
const persistedPbiId = useUserSettingsStore(
|
|
(s) => s.entities.settings.layout?.activePbis?.[productId] ?? undefined,
|
|
)
|
|
const persistedStoryId = useUserSettingsStore(
|
|
(s) => s.entities.settings.layout?.activeStories?.[productId] ?? undefined,
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (!hydrated) return
|
|
const store = useProductWorkspaceStore.getState()
|
|
// Schrijf alleen wanneer user-settings expliciet iets gekozen heeft
|
|
// (key aanwezig met string-waarde). null-key betekent 'bewust leeg' →
|
|
// we wissen lokale state. undefined-key (geen voorkeur) → niets doen.
|
|
if (persistedPbiId === undefined && persistedStoryId === undefined) return
|
|
|
|
if (persistedPbiId === null) {
|
|
store.setActivePbi(null)
|
|
return
|
|
}
|
|
if (persistedPbiId && store.context.activePbiId !== persistedPbiId) {
|
|
store.setActivePbi(persistedPbiId)
|
|
}
|
|
if (persistedStoryId && store.context.activeStoryId !== persistedStoryId) {
|
|
// setActivePbi triggert async cascade-restore die de oude hint kan
|
|
// herstellen; de daarop volgende setActiveStory bumpt activeRequestId
|
|
// en ongeldigt de cascade.
|
|
store.setActiveStory(persistedStoryId)
|
|
} else if (persistedStoryId === null) {
|
|
store.setActiveStory(null)
|
|
}
|
|
}, [hydrated, persistedPbiId, persistedStoryId])
|
|
|
|
return null
|
|
}
|