5.9 KiB
Scope-Owned Server State Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Prevent previous-account query and optimistic state from remaining renderable or writable after an authentication scope transition.
Architecture: The scope runtime is a fail-closed lifecycle authority. A bootstrap generation store owns a QueryClient and its concrete invalidation coordinator, swaps them only after the previous generation is closed, and exposes a stable invalidation facade plus a subscribable generation snapshot to React.
Tech Stack: TypeScript 7, React 19, TanStack Query 5, Vitest 4, Testing Library.
Global Constraints
- FENCED is synchronous and no previous-account UI may render after it.
- Mandatory participant or cache cleanup failure never publishes READY.
- Snapshot subscriber defects never prevent reset scheduling.
- Every READY generation owns a different QueryClient identity.
- Late work from an old generation cannot write through the stable coordinator.
- Preserve existing public application ports and dirty-worktree changes.
Task 1: Fail-closed scope lifecycle
Files:
- Modify:
src/contracts/server-state-scope.ts - Modify:
src/adapters/query-cache/server-state-scope-runtime.ts - Modify:
tests/unit/server-state-scope-runtime.test.ts
Interfaces:
-
Adds phase
FAILEDand lifecycle event{ kind: "FAILED"; generation: number }. -
Adds dependency callback
activateNextGeneration(): void | Promise<void>executed after old local reset and before READY. -
Add failing tests for a throwing snapshot listener, participant failure, reset failure, and next-generation activation failure.
-
Run
corepack pnpm exec vitest run tests/unit/server-state-scope-runtime.test.tsand confirm each new behavior fails for the intended reason. -
Notify listeners from a stable snapshot with per-listener exception isolation.
-
Continue all cleanup participants, remember the first mandatory failure, and always attempt local reset.
-
Close old identities after cleanup, then publish FAILED if any mandatory step failed.
-
Call
activateNextGenerationonly after successful reset and publish READY only after it succeeds. -
Re-run the focused unit test and confirm all lifecycle cases pass.
Task 2: Render fence surface synchronously
Files:
- Modify:
src/presentation/adapters/query/server-state-scope-provider.tsx - Create:
tests/component/server-state-scope-provider.test.tsx
Interfaces:
-
ServerStateScopeProvidersubscribes to both snapshot and phase through one store notification. -
Props add optional
transitionFallback?: ReactNode; default isnull. -
Existing
useServerStateScope()continues returningCacheScopeSnapshotfor feature hooks. -
Add a failing component test that renders private child data, triggers a deferred reset, and expects the child to disappear in the same
act()turn. -
Add a failing test that FAILED never remounts children.
-
Run the new component test and confirm old children remain with the current provider.
-
Subscribe in the provider and render children only for READY; render the supplied fallback for FENCED/FAILED and nothing after DISPOSED.
-
Re-run the test and confirm no old-data frame is observable.
Task 3: QueryClient generation store and stable coordinator facade
Files:
- Create:
src/bootstrap/server-state-generation-store.ts - Modify:
src/bootstrap/runtime-adapters.ts - Modify:
src/bootstrap/runtime-application.tsx - Modify:
tests/unit/runtime-adapters.test.ts - Modify:
tests/component/runtime-application.test.tsx
Interfaces:
-
ServerStateGenerationSnapshot = { generation: number; queryClient: QueryClient; coordinator: QueryInvalidationCoordinator; crossContextStatus(): CrossContextInvalidationStatus }. -
Store methods:
getSnapshot(),subscribe(listener),activateNext(),resetCurrent(),dispose(). -
A stable
QueryInvalidationCoordinatorfacade delegates only to the current snapshot and fences delegates by generation. -
Add a failing unit test that captures the initial QueryClient, triggers session transition, completes reset, and expects a different current QueryClient.
-
Add a failing test that an old captured coordinator cannot invalidate after activation.
-
Add a component test that QueryClientProvider receives and renders against the new generation.
-
Implement generation factory ownership in bootstrap; create cross-context transport and concrete coordinator per generation.
-
Pass
resetCurrentandactivateNextto the scope runtime in the required order. -
Make
RuntimeApplicationsubscribe to the generation store and key the QueryClient provider by generation. -
Dispose the current generation and store exactly once during application shutdown.
-
Run runtime-adapter and runtime-application tests until all pass.
Task 4: Conditional validator and cleanup ordering
Files:
- Modify:
src/bootstrap/runtime-adapters.ts - Modify:
tests/unit/runtime-adapters.test.ts
Interfaces:
-
Conditional validator clearing becomes an ordered mandatory scope participant rather than a general snapshot subscriber.
-
Add a failing order test proving validator clear occurs after admission fence and before cache disposal.
-
Register the validator closer with an explicit order and remove the generic subscription.
-
Re-run runtime-adapter tests and verify each closer executes once.
Task 5: Verification
- Run scope runtime, provider, runtime-adapter, runtime-application, and application-query focused suites.
- Run
corepack pnpm check:typesandcorepack pnpm lint. - Run the complete non-browser suite.
- Record browser-only follow-up separately; do not claim it passed without Playwright evidence.