# State Tree Architecture (MANDATORY)

All application state lives in a single hierarchical state tree. Components are pure renderers — they read from the tree via getter functions and dispatch actions. No component-local `$state` except DOM refs (`bind:this`).

## Tree Structure

```
AppState (root)  →  app-state.svelte.ts
├── theme, locale, appReady
├── ui: settings, palette, search, wizard, notifications
├── workspace
│   ├── groups[], activeGroupId
│   └── projects[] (per project ↓)
│       ├── agent: session, messages, status, cost
│       ├── terminals: tabs[], collapsed, activeTabId
│       ├── files: tree, selectedPath, content, dirty
│       ├── comms: channels[], messages[], mode
│       ├── tasks: items[], dragTarget
│       └── tab: activeTab, activatedTabs
└── health: per-project trackers, attention queue
```

## Rules

### 1. State ownership follows the tree

Each node owns its children. A node NEVER reads siblings or ancestors directly — it receives what it needs via getter functions scoped to its level.

```typescript
// RIGHT — scoped access through parent
function getProjectAgent(projectId: string) {
  return getProjectState(projectId).agent;
}

// WRONG — cross-branch reach
function getAgentForComms() {
  return agentStore.getSession(commsStore.activeAgentId);
}
```

### 2. Actions bubble up, state flows down

Components dispatch actions to the store that owns the state. State changes propagate downward via Svelte's reactive reads.

```typescript
// Component dispatches action
onclick={() => projectActions.setActiveTab(projectId, 'files')}

// Store mutates owned state
function setActiveTab(projectId: string, tab: ProjectTab) {
  getProjectState(projectId).tab.activeTab = tab;
}
```

### 3. One store file per tree level

| Level | File | Owns |
|-------|------|------|
| Root | `app-state.svelte.ts` | theme, locale, appReady |
| UI | `ui-store.svelte.ts` | drawers, overlays, modals |
| Workspace | `workspace-store.svelte.ts` | groups, projects list |
| Project | `project-state.svelte.ts` | per-project sub-states |
| Health | `health-store.svelte.ts` | trackers, attention |

### 4. Per-project state is a typed object, not scattered stores

```typescript
interface ProjectState {
  agent: AgentState;
  terminals: TerminalState;
  files: FileState;
  comms: CommsState;
  tasks: TaskState;
  tab: TabState;
}

// Accessed via:
function getProjectState(id: string): ProjectState
```

### 5. No `$state` in components

Components use ONLY:
- `$props()` for parent-passed values
- Getter functions from stores (e.g., `getProjectAgent(id)`)
- Action functions from stores (e.g., `projectActions.sendMessage(id, text)`)
- `bind:this` for DOM element references (the sole exception)
- Ephemeral interaction state (hover, drag coordinates) via plain `let` — NOT `$state`

### 6. No `$derived` with new objects (Rule 57)

All computed values are plain functions. See Rule 57 for details.

### 7. Initialization in `onMount`, never `$effect`

All async data loading, timer setup, and event listener registration goes in `onMount`. See Rule 57.

## Adding New State

1. Identify which tree node owns it
2. Add the field to that node's interface
3. Add getter + action functions in the store file
4. Components read via getter, mutate via action
5. NEVER add `$state` to a `.svelte` component file
