feat(ST-1369): screen-state module — ScreenState + deriveScreenState()

Pure afleidingslaag die de verspreide schermstaat-derivatie van de Product
Backlog page consolideert tot één testbaar ScreenState-model. Nog geen
consumers — die volgen in T-1035/T-1036.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-15 01:21:31 +02:00
parent e10b010884
commit 07b3cf6822

View file

@ -0,0 +1,33 @@
// Expliciete schermstaat voor de Product Backlog page.
//
// Consolideert de vandaag verspreide schermstaat-afleiding (page.tsx,
// sprint-switcher.tsx, new-sprint-trigger.tsx, save-sprint-button.tsx) tot één
// pure, testbare functie. Zie docs/architecture/product-backlog-workflow.md,
// sectie "To-be: expliciete state machine".
//
// PRODUCT_NOT_ACTIVE en DEMO_MODE blijven bewust BUITEN ScreenState — het zijn
// cross-cutting gates, geen knopen in de state machine.
export type ScreenState =
| { kind: 'NO_SPRINT' }
| { kind: 'DRAFT' }
| { kind: 'ACTIVE'; building: boolean }
| { kind: 'EDITING'; building: boolean }
export interface ScreenStateInput {
activeSprintItem: { id: string } | null // SSR-prop uit page.tsx
buildingSprintIds: string[] // SSR-prop uit page.tsx
hasPendingDraft: boolean // user-settings store
pendingAdds: string[] // product-workspace store: sprintMembership.pending.adds
pendingRemoves: string[] // product-workspace store: sprintMembership.pending.removes
}
export function deriveScreenState(i: ScreenStateInput): ScreenState {
if (i.hasPendingDraft) return { kind: 'DRAFT' } // draft wint van alles
if (i.activeSprintItem) {
const building = i.buildingSprintIds.includes(i.activeSprintItem.id)
const dirty = i.pendingAdds.length > 0 || i.pendingRemoves.length > 0
return dirty ? { kind: 'EDITING', building } : { kind: 'ACTIVE', building }
}
return { kind: 'NO_SPRINT' }
}