> For the complete documentation index, see [llms.txt](https://faction-os.gitbook.io/faction-os-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://faction-os.gitbook.io/faction-os-docs/.spec_system/archive/sessions/phase22-session05-store-folding-and-selectors/spec.md).

# Session Specification

**Session ID**: `phase22-session05-store-folding-and-selectors` **Phase**: 22 - Projection Foundation **Status**: Not Started **Created**: 2026-07-05 **Base Commit**: ec25d6f1b172b9f8cc32f8f805e510c8e0cbef63 **Package**: apps/web **Package Stack**: TypeScript

***

## 1. Session Overview

This session wires the Phase 22 `GameProjection` aggregate into the `apps/web` Zustand store. The store will hydrate a browser-local projection snapshot at initialization, fold each live server event exactly once through the pure reducer, and expose narrow selectors for projection, alert focus, and Legion state.

It is next because Sessions 01-04 created and validated the projection shape, reducer slices, parser, storage helpers, and camp-link boundary. Store folding must exist before the later persistence lifecycle, replay/mock honesty, and erasure sessions can prove browser-local behavior from the public store seams.

The work stays intentionally non-visual. It adds no battlefield rendering, audio, toasts, hosted persistence, protocol events, or second game state authority. `gameProjection.ts` remains the owner of projection logic; `useGameStore.ts` owns hydration, context derivation, event folding, selectors, and reset memory integration.

***

## 2. Objectives

1. Add `gameProjection: GameProjection` to `GameStore` and initialize it from `loadPersistedGameProjection` before live event folding.
2. Add one store-local `foldGameProjection` helper that derives `ctx.now`, `ctx.isReplaying`, and `ctx.mockEnabled` from current store/settings state.
3. Fold each `applyEvent(e)` server event exactly once after replay capture and before the existing event switch mutates other store branches.
4. Export narrow `selectGameProjection`, `selectGameAlertFocus`, and `selectGameLegion` helpers and prove reset-to-seed memory behavior with focused store tests.

***

## 3. Prerequisites

### Required Sessions

* [x] `phase22-session01-projection-contract` - Provides `GameProjection`, parser, storage helpers, and neutral initial state.
* [x] `phase22-session02-hero-attention-reducer` - Provides attention and alert-focus reducer behavior.
* [x] `phase22-session03-legion-enemy-reducer` - Provides live enemy reducer behavior and alert priority.
* [x] `phase22-session04-camp-link-boundaries` - Provides pending-link and camp-link reducer behavior needed before store folding.

### Required Tools Or Knowledge

* npm workspace commands from `.spec_system/CONVENTIONS.md`.
* Existing `apps/web/src/store/useGameStore.ts` replay-buffer capture and `applyEvent` switch structure.
* Existing `apps/web/src/store/useSettingsStore.ts` `mockEnabled` state.
* Existing `apps/web/src/lib/gameProjection.ts` reducer, parser, persistence key, and storage helpers.

### Environment Requirements

* Node 26.2.0+ and npm 11.16.0+ per project conventions.
* Vitest with happy-dom localStorage support through the existing root test setup.
* No database, migration, server, hosted service, Worker, or credential dependency applies.

***

## 4. Scope

### In Scope (MVP)

* The web store owns a `gameProjection: GameProjection` field initialized from `loadPersistedGameProjection()` so any existing valid browser snapshot is available before live events fold.
* `applyEvent(e)` calls one folding helper after replay capture and before the existing event switch, with reducer context derived from `new Date().toISOString()`, `replayingSinceMs`, and `useSettingsStore.getState().mockEnabled`.
* Store folding uses `reduceGameProjection` and only updates `gameProjection` when the reducer returns a different reference, preserving existing switch-side mutations.
* `resetToSeed()` leaves a fresh in-memory projection using `createInitialGameProjection()` and does not remove storage in this session.
* `selectGameProjection`, `selectGameAlertFocus`, and `selectGameLegion` are exported with narrow `Pick`-based input types from `apps/web/src/store/useGameStore.ts`.
* Focused store tests prove one-event folding, selector reads, replay-context derivation, mock-context derivation, persisted hydration, malformed-storage recovery, and reset memory behavior.

### Out Of Scope (Deferred)

* Debounced persistence writes, max-latency flushes, page lifecycle flushes, synchronous projection flush test helper, and projection storage removal in `resetToSeed()` - Reason: Session 06 owns persistence lifecycle.
* Browser-local erasure inventory and docs closeout - Reason: Session 08 owns erasure coverage and documentation alignment.
* Replay/reconnect/mock anti-farming regression matrix - Reason: Session 07 owns broad honesty regression coverage after persistence exists.
* New UI rendering, battlefield presentation, audio/SFX, toast/animation queues, hosted persistence, protocol events, server routes, or a second game-state authority - Reason: Phase 22 only establishes projection state authority.

***

## 5. Technical Approach

### Architecture

Import the projection helpers and types into `apps/web/src/store/useGameStore.ts`, then add `gameProjection` to the `GameStore` interface and initial Zustand state. The initializer will call `loadPersistedGameProjection()` directly so malformed or unavailable storage recovers through the parser already validated in `gameProjection.ts`.

Add a small store-local `foldGameProjection` helper near the replay persistence helpers. It will read the current projection, build the reducer context, call `reduceGameProjection`, and commit the new projection only when it changed by reference. `applyEvent(e)` will invoke this helper once after replay capture/scheduling and before the existing `switch (e.type)` so projection folding does not depend on later store mutations.

Selectors will follow the existing exported-selector pattern in `useGameStore.ts`: small pure functions with narrow `Pick` input types. Tests will use the public store API, `localStorage`, fixed events, and `useSettingsStore.setState` to exercise hydration, folding, replay, mock, and reset behavior without rendering components.

### Design Patterns

* Store boundary integration: Keep pure projection logic in `gameProjection.ts`; keep store lifecycle and settings-derived context in `useGameStore.ts`.
* Copy-on-write update guard: Only call `set({ gameProjection: next })` when the reducer returns a changed projection reference.
* Public selector helpers: Match existing `selectQuestBoard*` style while using `Pick` inputs to avoid exposing the full store contract.
* Focused happy-dom tests: Use a dedicated store test file to avoid broad component rendering and wall-clock delays.

***

## 6. Deliverables

### Files To Create

| File                                         | Purpose                                                                                                                  | Est. Lines |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `apps/web/tests/gameProjectionStore.test.ts` | Focused public store tests for projection hydration, folding, selectors, replay/mock context, and reset memory behavior. | \~220      |

### Files To Modify

| File                                 | Changes                                                                                                                           | Est. Lines |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `apps/web/src/store/useGameStore.ts` | Add projection imports, store field, selector helpers, folding helper, `applyEvent` integration, and reset memory initialization. | \~90       |
| `apps/web/src/lib/gameProjection.ts` | Add only narrowly needed exported helper or type adjustments if store tests reveal an existing selector/test seam is missing.     | \~20       |

***

## 7. Success Criteria

### Functional Requirements

* [ ] Store initialization loads a valid `localStorage["factionos-game-v1"]` projection through `loadPersistedGameProjection()`.
* [ ] Store initialization recovers to a fresh projection when the persisted value is missing, malformed, or unavailable.
* [ ] `applyEvent(e)` folds the projection exactly once per server event and does not depend on mutations performed by the existing switch cases.
* [ ] Replay context is derived from `replayingSinceMs` so replayed events fold with `ctx.isReplaying: true`.
* [ ] Mock context is derived from `useSettingsStore.getState().mockEnabled`.
* [ ] `resetToSeed()` leaves a fresh in-memory projection and does not remove the projection storage key in this session.
* [ ] Selectors expose the projection, alert focus, and Legion state without requiring consumers to depend on the full `GameStore` interface.

### Testing Requirements

* [ ] `apps/web/tests/gameProjectionStore.test.ts` covers hydration, malformed-storage recovery, one-event folding, selectors, replay context, mock context, and reset memory behavior.
* [ ] Existing reducer coverage in `apps/web/tests/gameProjection.test.ts` still passes.
* [ ] `npm test -- apps/web/tests/gameProjectionStore.test.ts apps/web/tests/gameProjection.test.ts` passes.
* [ ] `npm --workspace @factionos/web run typecheck` passes.

### Non-Functional Requirements

* [ ] Projection state remains serializable, bounded, and owned by `gameProjection.ts`.
* [ ] No new UI, protocol, server, hosted persistence, audio, or second projection authority is introduced.
* [ ] Store folding does not write browser storage; persistence writes remain deferred to Session 06.

### Quality Gates

* [ ] All files ASCII-encoded.
* [ ] Unix LF line endings.
* [ ] Code follows project conventions.
* [ ] `npm exec -- biome check apps/web/src/store/useGameStore.ts apps/web/src/lib/gameProjection.ts apps/web/tests/gameProjectionStore.test.ts` passes for changed files.
* [ ] Primary user-facing surfaces are N/A because this session changes store/test code only.

***

## 8. Implementation Notes

### Working Assumptions

* Store selector placement: Existing store selectors such as `selectQuestBoardIdleCards` live in `apps/web/src/store/useGameStore.ts`, so the projection selectors should be exported from the same file with narrow `Pick` inputs.
* Hydration-only storage behavior: Session 05 requires initialization from `localStorage["factionos-game-v1"]`, while Session 06 owns writes and lifecycle flushing. Planning can proceed by loading through existing helpers without adding any new persistence scheduling.
* Dedicated store test file: The current repo has `apps/web/tests/gameProjection.test.ts` for pure reducer behavior and `apps/web/tests/replayPersist.test.ts` for replay persistence. A new `gameProjectionStore.test.ts` keeps Session 05 coverage focused on store integration without mixing persistence lifecycle scope.

### Conflict Resolutions

* Reset memory vs storage removal: Session 05 says `resetToSeed()` leaves a fresh projection in memory, while Session 06 says storage removal is finished with persistence lifecycle work. The chosen interpretation is to reset in-memory projection only in Session 05 and leave `localStorage["factionos-game-v1"]` untouched until Session 06.

### Key Considerations

* `useSettingsStore` already defaults `mockEnabled` to true in development settings, so tests that assert non-mock behavior must set it explicitly.
* `applyEvent` currently captures replay entries before the event switch; projection folding should sit after that capture block and before the switch.
* Existing `loadPersistedGameProjection` and `parsePersistedGameProjection` already defend against malformed storage.
* Later persistence must be able to tell when folding changed projection state, so reference-change behavior should stay explicit.

### Potential Challenges

* Store module import-time hydration can make tests order-sensitive: clear `localStorage`, reset store state, and use direct `useGameStore.setState` where needed for deterministic fixtures.
* Folding with `Date.now()` or `new Date()` can make assertions brittle: assert semantic state changes or inject fixed persisted snapshots rather than exact live timestamps where possible.
* Mock-enabled default can hide expected folds or persistence assumptions: set `useSettingsStore` in every relevant test.

### Relevant Considerations

* \[P03] **Stable docs are the current contract**: Use live PRD, Phase 22 stubs, `apps/web/README_web.md`, and package-local tests as current truth.
* \[P18-apps/web] **Pure normalization before store mutation**: Fold through the projection reducer before unrelated store branch mutations so malformed entries cannot blank the cockpit.
* \[P20] **Broad privacy gates are release-critical**: Do not add raw prompts, terminal output, file contents, tokens, secrets, broad paths, or diagnostics to UI, exports, logs, or normal store-derived rows.
* \[P08] **Full trusted erasure remains no-claim**: Hydrating browser-local projection state must not imply broad trusted erasure or hosted storage coverage.

### Behavioral Quality Focus

Checklist active: Yes Top behavioral risks for this session:

* Store event handling must fold external WebSocket events with explicit replay/mock context and preserve existing event switch behavior.
* Store state mutation must avoid duplicate projection folding and only update the projection field when the reducer returns changed state.
* Browser storage reads must recover from malformed or unavailable storage through the existing projection parser without blanking the store.

***

## 9. Testing Strategy

### Unit Tests

* Add focused store tests in `apps/web/tests/gameProjectionStore.test.ts` for selector outputs, reset memory state, and direct store state fixtures.
* Reuse typed protocol event builders for `internal_hero_spawn`, `awaiting_input`, `build_error`, or `tool_result` cases that visibly change projection state.

### Integration Tests

* Test `useGameStore.getState().applyEvent()` as the integration seam between replay capture, projection folding, settings context, and existing store switch mutations.
* Test persisted hydration by writing a valid projection JSON to `GAME_PROJECTION_STORAGE_KEY` before importing or resetting the store fixture in a way that exercises the initializer.

### Runtime Verification

* Run `npm test -- apps/web/tests/gameProjectionStore.test.ts apps/web/tests/gameProjection.test.ts`.
* Run `npm --workspace @factionos/web run typecheck`.
* Run `npm exec -- biome check apps/web/src/store/useGameStore.ts apps/web/src/lib/gameProjection.ts apps/web/tests/gameProjectionStore.test.ts`.

### Edge Cases

* Malformed persisted projection falls back to a fresh projection and does not throw.
* Unknown events return the same projection reference and do not force a store update.
* Replay-mode folding does not increment replay-guarded projection counters.
* Mock-mode context is passed to the reducer without adding persistence writes.
* `resetToSeed()` resets memory but leaves storage removal for Session 06.

***

## 10. Dependencies

### Other Sessions

* Depends on: `phase22-session04-camp-link-boundaries`
* Depended by: `phase22-session06-persistence-lifecycle`, `phase22-session07-honesty-regression-tests`, `phase22-session08-erasure-docs-and-validation`

***

## Next Steps

Run the `implement` workflow step to begin implementation.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://faction-os.gitbook.io/faction-os-docs/.spec_system/archive/sessions/phase22-session05-store-folding-and-selectors/spec.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
