Load/render workspace alignment (#182)
* docs: plan load render workspace alignment * fix: normalize workspace status hydration * fix: avoid duplicate backlog hydration load * refactor: use sprint store active story * refactor: migrate solo to workspace store * chore: stabilize verification ignores
This commit is contained in:
parent
98ee05d458
commit
3b5cee823c
28 changed files with 1845 additions and 577 deletions
201
docs/plans/load-render-improvement-plan-2026-05-10.md
Normal file
201
docs/plans/load-render-improvement-plan-2026-05-10.md
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
---
|
||||
title: "Verbeterplan load/render Product Backlog, Sprint en Solo"
|
||||
date: 2026-05-10
|
||||
status: draft
|
||||
scope: ["Product Backlog", "Sprint", "Solo", "workspace stores", "realtime resync"]
|
||||
source_review: "../recommendations/load-render-implementation-review-2026-05-10.md"
|
||||
chosen_solo_option: "5B - Solo workspace-store migratie"
|
||||
---
|
||||
|
||||
# Verbeterplan load/render Product Backlog, Sprint en Solo
|
||||
|
||||
## Doel
|
||||
|
||||
Maak de load/render-flow van Product Backlog, Sprint en Solo voorspelbaar, gelijkvormig en goedkoper:
|
||||
|
||||
- geen dubbele initial loads;
|
||||
- een expliciet status-contract tussen server, API, stores en UI;
|
||||
- consistente hydration/resync na reconnect, tab-visible en refresh;
|
||||
- minder stale render state in Solo;
|
||||
- duidelijke store-eigenaarschap per scherm.
|
||||
|
||||
## Uitgangspunten
|
||||
|
||||
- De route/API-boundary mag lowercase API-statussen blijven gebruiken.
|
||||
- De interne Product/Sprint workspace UI verwacht nu story/task statussen als DB `UPPER_SNAKE`.
|
||||
- Product Backlog en Sprint zijn de referentie voor het gewenste patroon: server snapshot, client hydration wrapper, workspace-store, SSE, directe store-resync.
|
||||
- Solo hoeft niet in dezelfde grote store te worden gemigreerd in de eerste stap, maar zijn refresh-hydration moet wel correct worden.
|
||||
|
||||
## Fase 1 - Status-contract vastleggen en afdwingen
|
||||
|
||||
### Stap 1.1 - Leg het interne contract vast
|
||||
|
||||
Besluit en documenteer:
|
||||
|
||||
- PBI-status in Product Backlog blijft API-lowercase zolang `PBI_STATUS_LABELS` en `PBI_STATUS_COLORS` daarop gebouwd zijn.
|
||||
- Story-status en task-status in Product/Sprint workspace-stores zijn intern `UPPER_SNAKE`.
|
||||
- API routes blijven lowercase teruggeven aan externe/REST clients.
|
||||
|
||||
### Stap 1.2 - Voeg adapters toe aan de workspace-store boundary
|
||||
|
||||
Maak kleine adapterfuncties voor API-responses voordat data in stores wordt gehydrateerd:
|
||||
|
||||
- Product workspace:
|
||||
- full backlog snapshot;
|
||||
- PBI stories;
|
||||
- story tasks;
|
||||
- task detail.
|
||||
- Sprint workspace:
|
||||
- sprint workspace snapshot;
|
||||
- story tasks;
|
||||
- task detail.
|
||||
|
||||
Gebruik bestaande mappers uit `lib/task-status.ts`, bijvoorbeeld `storyStatusFromApi` en `taskStatusFromApi`.
|
||||
|
||||
### Stap 1.3 - Voeg regressietests toe
|
||||
|
||||
Test minimaal:
|
||||
|
||||
- API lowercase `todo` wordt in task UI-store `TO_DO`;
|
||||
- API lowercase `in_sprint` wordt in story UI-store `IN_SPRINT`;
|
||||
- bestaande PBI lowercase status blijft lowercase;
|
||||
- Sprint `STATUS_CYCLE` krijgt nooit lowercase input vanuit de store.
|
||||
|
||||
## Fase 2 - Dubbele Product Backlog load verwijderen
|
||||
|
||||
### Stap 2.1 - Maak hydration eigenaar van de initial backlog snapshot
|
||||
|
||||
Pas Product Backlog aan naar hetzelfde eigenaarschap als Sprint:
|
||||
|
||||
- `BacklogHydrationWrapper` hydrateert snapshot;
|
||||
- wrapper zet ook `context.activeProduct`;
|
||||
- wrapper markeert `loadedProductId`;
|
||||
- `SetCurrentProduct` start op routes met eigen hydration geen full `ensureProductLoaded`.
|
||||
|
||||
### Stap 2.2 - Guard `setActiveProduct`
|
||||
|
||||
Voeg een guard toe zodat `setActiveProduct(product)` geen `ensureProductLoaded` start als:
|
||||
|
||||
- hetzelfde product al actief is;
|
||||
- `loading.loadedProductId === product.id`;
|
||||
- er al een volledige snapshot gehydrateerd is.
|
||||
|
||||
### Stap 2.3 - Meet en verifieer
|
||||
|
||||
Controleer in devtools/server logs:
|
||||
|
||||
- openen van Product Backlog doet geen extra `/api/products/:id/backlog` na de server-render;
|
||||
- navigeren tussen product routes laadt nog steeds correct;
|
||||
- restore hints voor laatste PBI/story/task blijven werken.
|
||||
|
||||
## Fase 3 - Sprint selectie gelijkvormig maken
|
||||
|
||||
### Stap 3.1 - Verplaats geselecteerde story naar de sprint workspace-store
|
||||
|
||||
Vervang lokale `selectedStoryId` in `SprintBoardClient` door:
|
||||
|
||||
- `useSprintWorkspaceStore((s) => s.context.activeStoryId)`;
|
||||
- `useSprintWorkspaceStore.getState().setActiveStory(storyId)`;
|
||||
- reset via `setActiveStory(null)` bij verwijderen uit sprint.
|
||||
|
||||
### Stap 3.2 - Laat `TaskList` active-context gebruiken
|
||||
|
||||
Maak `TaskList` gelijkvormig met Product Backlog:
|
||||
|
||||
- lees taken via `selectTasksForActiveStory`;
|
||||
- behoud `storyId` alleen als fallback of verwijder de prop;
|
||||
- zorg dat `resyncActiveScopes` nu de actieve story/task werkelijk kan meenemen.
|
||||
|
||||
### Stap 3.3 - Restore-hints testen
|
||||
|
||||
Verifieer:
|
||||
|
||||
- story-selectie blijft behouden na refresh/reconnect;
|
||||
- task-paneel toont dezelfde story na tab-visible resync;
|
||||
- verwijderen van de actieve story reset taakpaneel netjes.
|
||||
|
||||
## Fase 4 - Solo refresh-hydration correct maken
|
||||
|
||||
### Stap 4.1 - Vervang task-id-only dependency
|
||||
|
||||
Vervang `taskKey = initialTasks.map(t => t.id).join(',')` door een render-relevante fingerprint, bijvoorbeeld:
|
||||
|
||||
- `id`;
|
||||
- `status`;
|
||||
- `sort_order`;
|
||||
- `title`;
|
||||
- `implementation_plan`;
|
||||
- `story_id`;
|
||||
- `story_title`;
|
||||
- `story_code`;
|
||||
- `task_code`;
|
||||
- relevante verify/queue velden.
|
||||
|
||||
Of hydrateer op iedere nieuwe `initialTasks` prop als performance acceptabel is.
|
||||
|
||||
### Stap 4.2 - Sync unassigned stories uit props
|
||||
|
||||
Voeg een effect toe die `unassignedStories` bijwerkt wanneer `initialUnassigned` inhoudelijk wijzigt.
|
||||
|
||||
### Stap 4.3 - Sorteer solo kolommen expliciet
|
||||
|
||||
Render `columnTasks` gesorteerd op `sort_order` en daarna stabiel op code/titel/id. Vertrouw niet op object insertion order.
|
||||
|
||||
### Stap 4.4 - Test gemiste event scenario's
|
||||
|
||||
Test:
|
||||
|
||||
- tab hidden, task status wijzigt extern, tab visible: kaart staat in juiste kolom;
|
||||
- reconnect met dezelfde task ids maar gewijzigde titel/status: UI update;
|
||||
- nieuwe unassigned story verschijnt na refresh;
|
||||
- gewijzigde `sort_order` past de render-volgorde aan.
|
||||
|
||||
## Fase 5 - Solo naar een gelijkvormig workspace-store patroon
|
||||
|
||||
Gekozen route: **Optie B**. Solo wordt naar een workspace-store patroon gemigreerd dat aansluit op Product Backlog en Sprint.
|
||||
|
||||
### Optie B - Grote stap
|
||||
|
||||
Migreer Solo naar een workspace-store patroon vergelijkbaar met Product/Sprint:
|
||||
|
||||
- normalized entities;
|
||||
- active sprint/product context;
|
||||
- loaded scopes;
|
||||
- resync methods;
|
||||
- realtime event adapters.
|
||||
|
||||
Concrete taken:
|
||||
|
||||
- Introduceer `stores/solo-workspace/{types,selectors,store}.ts`.
|
||||
- Introduceer een `SoloHydrationWrapper` die server snapshot en actieve context hydrateert.
|
||||
- Laat `SoloBoard` renderen vanuit selectors in de solo workspace-store.
|
||||
- Verplaats realtime event handling en job/worker status naar de solo workspace-store.
|
||||
- Vervang `router.refresh()` als primaire resync door `resyncActiveScopes`.
|
||||
- Houd route refresh alleen over als expliciete fallback voor onbekende events of navigatiecases.
|
||||
|
||||
## Fase 6 - Observability en performance check
|
||||
|
||||
Voeg tijdelijk of permanent meetpunten toe:
|
||||
|
||||
- log of dev-only counter voor hydration calls per scherm;
|
||||
- log of dev-only counter voor API `ensure*Loaded` calls;
|
||||
- React Profiler rond Product Backlog/Sprint/Solo pane containers;
|
||||
- netwerkcheck op dubbele fetches.
|
||||
|
||||
Acceptatiecriteria:
|
||||
|
||||
- Product Backlog doet bij eerste openen maximaal een server snapshot plus SSE connect, geen extra full-backlog client fetch.
|
||||
- Product en Sprint stores bevatten geen lowercase story/task statussen.
|
||||
- Solo refresh verwerkt bestaande tasks met gewijzigde velden.
|
||||
- Product Backlog, Sprint en Solo hebben per scherm precies een duidelijke eigenaar voor initial hydration.
|
||||
|
||||
## Voorgestelde implementatievolgorde
|
||||
|
||||
1. Status adapters en tests toevoegen.
|
||||
2. Product Backlog dubbele load verwijderen.
|
||||
3. Sprint active story selectie naar store verplaatsen.
|
||||
4. Solo workspace-store introduceren en hydrateren.
|
||||
5. Solo realtime/resync naar workspace-store verplaatsen.
|
||||
6. Performance/netwerk verifiëren.
|
||||
|
||||
Deze volgorde beperkt risico: eerst het data-contract, daarna de extra load, daarna gelijkvormigheid en Solo-resync.
|
||||
Loading…
Add table
Add a link
Reference in a new issue