1## Context23The `app` package contains the root Bubble Tea model for PuzzleTea. It already uses a useful `screenModel` and `screenAction` pattern: menu-like screens update local component state and return actions that the root model applies. However, the root model still owns several distinct concerns at once:45- The current route and cached screen instances.6- Window sizing and active screen resizing.7- Global key behavior for normal screens, active puzzles, pending generation, and pending export.8- Active puzzle lifecycle, including spawn start/completion, save/load, completion persistence, and read-only weekly review.9- Workflow setup for create, normal mode selection, daily, seeded, weekly, export, help, stats, and theme flows.10- Shell state such as notices, debug rendering, theme/help toggles, store/config dependencies, and the shared spinner.1112The refactor should keep the public Bubble Tea surface stable while making those ownership boundaries explicit enough that future UI flows can be added without expanding `model.Update` or scattering session persistence rules.1314## Goals / Non-Goals1516**Goals:**1718- Keep `model.Update` focused on top-level dispatch: async messages, global shell keys, active game delegation, screen updates, and action application.19- Make route/screen management responsible for current route, cached screen models, window sizing, active screen lookup, initialization, and resize behavior.20- Make session lifecycle code the single owner of game spawning, spawn cancellation, spawn completion, saved-record import, active game update, completion persistence, and save-on-exit.21- Keep screen models dependency-light: screens should continue to emit typed actions instead of directly reaching into the store, config, session controller, or root model internals.22- Preserve current behavior for all app flows.23- Add focused tests around routing/session behavior that could regress during the refactor.2425**Non-Goals:**2627- No changes to game package APIs, `game.Gamer`, registry/catalog behavior, persistence schema, or CLI flags.28- No visual redesign of menus, screens, exports, or puzzle views.29- No replacement of Bubble Tea or Bubbles components.30- No broad package split unless a smaller `app`-internal type cannot express the boundary cleanly.3132## Decisions3334### Keep One Bubble Tea Root Model3536The app should continue exposing a single root Bubble Tea model. Internally, the root can delegate to smaller app-owned types, but it should remain the integration point for Bubble Tea messages and views.3738Alternative considered: make every app screen a full independent Bubble Tea model with store/config/session dependencies. That would reduce the root's visible size, but it would scatter persistence and lifecycle behavior across many screens. The current `screenAction` pattern is better: screens own component interaction, while app-level behavior stays centralized.3940### Introduce an App-Internal Route/Screen Manager4142Route and screen mechanics should be isolated from workflow behavior. A route/screen manager can own:4344- Current `viewState`.45- Cached `map[viewState]screenModel`.46- Active screen lookup.47- Screen initialization through the existing registry.48- Window dimensions and active-screen resize.49- Route transitions that only change display state.5051The manager should not know how to create puzzles, save games, export packs, or apply theme changes. It should receive enough shell state to construct screens, then return updated screen state to the root.5253Alternative considered: leave these methods on `model` but move them into more files. That would make files shorter without changing ownership, so it would not materially improve reasoning.5455### Keep Screens as Intent Emitters5657Screens should continue returning `screenAction` values from `Update`. Actions should remain typed app intents, not generic strings. This keeps screen tests focused on interaction and allows root/controller tests to cover app behavior.5859Where actions currently perform substantial workflow setup, the action should become a small adapter into the appropriate controller. For example, mode selection should delegate session start details instead of constructing all spawn metadata inline.6061Alternative considered: have screens call controller methods directly. That would make individual actions smaller but would require injecting app dependencies into screens and would weaken the current separation.6263### Make Session Lifecycle the Owner of Active Game State6465The session controller should own:6667- Starting normal, Elo, and seeded generation.68- Cancelling pending generation.69- Ignoring stale spawn completions.70- Activating generated or imported games.71- Applying help/window-size messages to newly activated games.72- Updating active games.73- Persisting completed puzzles.74- Saving or clearing games on escape/quit.75- Handling read-only game opens.7677Workflow handlers may decide *which* puzzle to open or generate, but once that decision is made they should enter session code through higher-level methods rather than duplicating spawn request construction and save/resume rules.7879Alternative considered: keep workflow-specific spawn setup in handler files. That keeps local context near each flow, but the duplicated pattern of "check record, maybe resume, otherwise spawn, then persist" makes session behavior harder to audit.8081### Split Global Key Handling by Route Ownership8283Global key handling should route to the domain that owns the current state:8485- Pending generation: session controller handles escape/quit behavior and spawn cancellation.86- Pending export: export controller/handler handles escape/quit behavior and export cancellation.87- Active game: session controller handles save-on-escape, abandon-on-quit, reset, help toggle propagation, debug update, and weekly advancement entry points.88- Normal screens: shell/global keys handle quit, debug toggle, and full-help toggle.8990This keeps `handleGlobalKey` from accumulating per-route workflow details.9192Alternative considered: keep a single root `handleGlobalKey` switch. It is straightforward today, but it is already mixing shell, export, session, and weekly behavior.9394### Preserve Behavior Before Improving Shape Further9596The refactor should be incremental and behavior-preserving. It is acceptable to leave some workflow-specific logic on the root temporarily if moving it would enlarge the diff or weaken tests. The important outcome is clearer ownership for the root update loop and session lifecycle.9798## Risks / Trade-offs99100- **Risk: Moving state between structs can accidentally drop screen component state.** → Keep the existing screen cache behavior and add tests around returning to screens after resize/navigation where practical.101- **Risk: Session controller methods can become another large object.** → Limit it to active game/spawn/save/load lifecycle. Keep create/daily/seed/weekly selection logic outside unless it directly affects session invariants.102- **Risk: Behavior-preserving refactors can hide regressions in keyboard handling.** → Add focused tests for escape/quit/reset/help/debug paths in generating, export-running, active-game, and normal-screen states.103- **Risk: Export remains large and may not fit the same controller shape immediately.** → Treat export as a separate workflow boundary and avoid mixing it into session lifecycle.104- **Risk: Over-abstracting routes could obscure simple app flows.** → Prefer small concrete types and typed methods over generic event buses or reflection-based routing.105106## Migration Plan1071081. Add tests that pin the current high-risk behavior before moving ownership.1092. Extract route/screen management with no behavior changes.1103. Move pending generation and active game key behavior into session-oriented methods.1114. Move duplicated spawn/open/resume behavior behind session-oriented entry points where the call sites become clearer.1125. Re-run `just fmt`, `just lint`, and `just test-short`.113114Rollback is straightforward because there are no data migrations or public API changes: revert the refactor commits if behavior diverges.115116## Graph Analysis117118A GitNexus knowledge graph analysis of the codebase is in `graph-analysis.md`. It provides concrete symbol locations, call-chain evidence, blast-radius data, and file inventory for this refactor. Key findings:119120- **11 symbols** depend on `model` struct directly (MEDIUM risk, contained in `app` + `cmd` packages).121- **3 wrapper functions** in `spawn.go` exist solely to bridge value-receiver `handleGlobalKey` to pointer-receiver `sessionController` — these can be eliminated by moving key handling into the controller.122- **`initScreen` has 13 callers** spanning both route transitions and workflow code — highest regression risk.123- **8 `screenAction` types** already implement the intent-emitter pattern cleanly; no changes needed there.124- **`export.go` is 1,720 lines** — the largest affected file; warrants its own future refactor, not this one.125126## Open Questions (resolved by graph analysis)127128- **Should create/daily/seed/weekly decision logic live in one helper?** No — each workflow constructs a different `spawnRequest` with different fields. The session controller already owns spawn mechanics; consolidating decision logic would add a layer without reducing duplication.129- **Should export get a controller in this change?** No — minimal cleanup only. Extract `exportRunningView` key handling into `app/export.go` as a function. Export's size (1,720 lines) warrants a dedicated refactor.