Problem
The global WizardSession and the TUI store have become a catch-all. Every program's state lives flat on one shared object, so the core keeps growing and infrastructure code carries product knowledge it shouldn't.
Today:
src/lib/wizard-session.ts — 404 lines, ~164 fields/members on the session interface.
src/ui/tui/store.ts — 944 lines, 29 explicit setters (each must remember to call emitChange()).
- Program-specific state is flat on the shared session regardless of which program is running, e.g.
mcpComplete / mcpOutcome / mcpInstalledClients / mcpSuggestedPromptsDismissed, slackStepDismissed / slackConnected, skillsComplete / skillId, … These exist on the session for an audit or revenue-analytics run that never touches MCP or Slack.
This violates the repo's own design discipline (AGENTS.md: product knowledge never enters infrastructure code). The store/session are machinery, but they currently know about MCP, Slack, skills, etc. Adding a program means widening the shared session and adding setters to the shared store — the bloat compounds.
Goal
Make the store and session composable and driven by program definitions (src/lib/programs/), so:
- The core session holds only program-agnostic state (install dir, credentials, detected framework, run phase, current screen).
- Each program contributes its own typed state slice (e.g.
mcp, slack, skills) declared alongside its program definition, mounted only when that program runs.
- The store composes setters/selectors from the active program's slices instead of hardcoding all 29.
- Screens read/write their program's slice, not a flat global.
Sketch (not prescriptive)
- A program definition declares an optional
state: { initial, setters } slice with a typed shape.
buildSession() composes core + the active program's slices; the store derives its setter surface from them (so emitChange() is wired once, generically).
- Migrate the obvious clusters first:
mcp*, slack*, skill* → owned by the programs that use them.
Acceptance criteria
Notes
- Read
.claude/skills/wizard-development/SKILL.md + references/ARCHITECTURE.md first — this is a structural change to the pipeline's core.
- Keep the nanostore single-shallow-copy + explicit-setter invariant; the change is about where slices are defined and composed, not how mutation/emit works.
Problem
The global
WizardSessionand the TUI store have become a catch-all. Every program's state lives flat on one shared object, so the core keeps growing and infrastructure code carries product knowledge it shouldn't.Today:
src/lib/wizard-session.ts— 404 lines, ~164 fields/members on the session interface.src/ui/tui/store.ts— 944 lines, 29 explicit setters (each must remember to callemitChange()).mcpComplete/mcpOutcome/mcpInstalledClients/mcpSuggestedPromptsDismissed,slackStepDismissed/slackConnected,skillsComplete/skillId, … These exist on the session for anauditorrevenue-analyticsrun that never touches MCP or Slack.This violates the repo's own design discipline (AGENTS.md: product knowledge never enters infrastructure code). The store/session are machinery, but they currently know about MCP, Slack, skills, etc. Adding a program means widening the shared session and adding setters to the shared store — the bloat compounds.
Goal
Make the store and session composable and driven by program definitions (
src/lib/programs/), so:mcp,slack,skills) declared alongside its program definition, mounted only when that program runs.Sketch (not prescriptive)
state: { initial, setters }slice with a typed shape.buildSession()composes core + the active program's slices; the store derives its setter surface from them (soemitChange()is wired once, generically).mcp*,slack*,skill*→ owned by the programs that use them.Acceptance criteria
WizardSessionno longer references program-specific concepts (MCP/Slack/skills/etc.).emitChange()stays centralized.pnpm build && pnpm testgreen; router/screen-resolution behavior unchanged.Notes
.claude/skills/wizard-development/SKILL.md+references/ARCHITECTURE.mdfirst — this is a structural change to the pipeline's core.