Scrum4Me/docs/plans/load-render-improvement-plan-2026-05-10.md
Janpeter Visser 3b5cee823c
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
2026-05-10 07:34:58 +02:00

7.3 KiB

title date status scope source_review chosen_solo_option
Verbeterplan load/render Product Backlog, Sprint en Solo 2026-05-10 draft
Product Backlog
Sprint
Solo
workspace stores
realtime resync
../recommendations/load-render-implementation-review-2026-05-10.md 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.