# Assistant Module — Codex (per FVW v8 §22 Behaviour-First Codex)

**Module ID:** `oscar.assistant`
**Module version:** 0.2.0
**Per FVW v8 v0.10.0 §22:** This codex contains C1-C9 contract items.
**Doctrinal basis:** §22 (7 items ratified) + §30 (C8 shape matrix) + v9-fvw (C9 state shape inventory).
**Status:** Constitutional behaviour contract.

---

## C1 — Behaviour Inventory

Every event handler, lifecycle hook, state transition, side effect enumerated with source line citations.

| Handler | Source:line | Purpose |
|---|---|---|
| `AssistantDock` render | `app/src/assistant/AssistantDock.tsx:34-90` | Root; wires 4-state dock |
| `Bar` tap handler | `app/src/assistant/Bar.tsx` | bar → sheet-min |
| `SheetMin` input handler | `app/src/assistant/SheetMin.tsx` | sheet-min → sheet-full on focus |
| `SheetFull` send handler | `app/src/assistant/SheetFull.tsx` | send prompt to askOscar() |
| `BackgroundNav` cross-view | `app/src/assistant/BackgroundNav.tsx` | Cross-view navigation bridges |
| `BackStack.push` | `app/src/assistant/BackStack.ts:1-43` | Push anchor with side 1000 |
| `BackStack.pop` | `app/src/assistant/BackStack.ts:1-43` | Pop with side 999 (preserves anchors per RT-1.2) |
| `applyTransition` | `app/src/assistant/state-machine.ts:1-73` | Compute next state from trigger |
| `findHeuristic` | `app/src/assistant/heuristics.ts:1-198` | Layer 1 dispatcher |
| `searchWeb` | `app/src/assistant/web-search.ts:1-144` | Layer 1.5 web search (opt-in DDG) |
| `summarise` | `app/src/assistant/data/index.ts` | Layer 2 data queries |
| `ConversationMemory.update` | `app/src/assistant/memory.ts:1-54` | Track conversation state across calls |
| `askOscar` | `app/src/assistant/ask.ts:1-108` | Single entrypoint for ALL AI calls |
| Action chip render | `app/src/assistant/SheetFull.tsx` | Render action chips (navigate / open-item / compose / export) |
| Action chip execute | `app/src/assistant/actions.ts:1-15` | Execute action (route / open / compose / export) |

**Lifecycle hooks:**
- `useAssistantDockContext` (`app/src/assistant/context.tsx`) — React context for state
- `useEffect` in `AssistantDock.tsx` for route-aware state reset

---

## C2 — State Machine with Transitions

**States (4):**
- `bar` (default, dormant)
- `sheet-min` (peek, quick input)
- `sheet-full` (open, conversation)
- `peek` (peek-panel, lifted view)

**Transitions:**

| From | Trigger | To | Conditions |
|---|---|---|---|
| bar | tap on Bar | sheet-min | always |
| bar | swipe-up | sheet-full | RT-2.4 |
| sheet-min | focus input | sheet-full | user input |
| sheet-min | dismiss | bar | always |
| sheet-full | dismiss | bar | always |
| sheet-full | peek button | peek | always |
| peek | release | bar | always |
| peek | expand | sheet-full | always |

**Transition validator:** `applyTransition(trigger: TransitionTrigger) => DockState`
**Back navigation:** `BackStack.pop` returns to `bar` while preserving all anchors (RT-1.2)

---

## C3 — Side Effect List

| Side effect | When | Persistence |
|---|---|---|
| IDB write via repo | When user clicks action chip → save/post/export | IDB (posts, notes, surveys, reports, trees, sites) |
| localStorage: provider | `askOscar` first call | `oscar:provider` |
| localStorage: theme | (handled by branding module) | `oscar:theme` |
| localStorage: brain-downloaded | (handled by brain module) | `oscar:brain-downloaded` |
| DOM update | Sheet open/close, anchor render, action chip render | ephemeral |
| Network: web-search opt-in | Layer 1.5 fires (user types "search the web for X") | ephemeral, no caching |
| Network: LLM API call | `askOscar` when active provider is anthropic/openai | per-call |

---

## C4 — Input Validation Contract

| Input | Validates | Rejection behaviour |
|---|---|---|
| prompt (string) | non-empty | returns `{ text: '', error: 'Empty prompt' }` |
| prompt (string) | not pure whitespace | same as empty |
| Writing prompt (matches WRITING_TRIGGER) | requires generative provider | returns error: 'Writing needs a generative LLM...' |
| history (string[]) | optional, defaults [] | not validated (passed through to adapter) |

**Validation location:** `app/src/assistant/ask.ts:50-90`

---

## C5 — Failure Modes

| Failure | Handling | Surfacing |
|---|---|---|
| Provider error (anthropic 401, openai 401) | Caught; returns friendly error | `AskResult.error` |
| Provider error (rate limit) | Caught; returns retry message | `AskResult.error` |
| IDB error (quota exceeded) | Repository pattern catches | `AskResult.error` + user alert |
| Web search CORS (DDG) | `intelligence/adapters/search.ts` returns false | Honest "search failed" message |
| Smart adapter Layer 9 (no match) | Returns ASK question | Echo user's words + offer action chips |
| Writing prompt + smart provider | Returns clear error | "Writing needs a generative LLM. Open Settings → AI provider..." |

---

## C6 — User Simulation List

C6 user simulations must be runnable as automated tests. Examples:

| # | User flow | Expected behaviour |
|---|---|---|
| USIM-1 | User taps Bar | State → sheet-min, input focused |
| USIM-2 | User types "classify a 15m oak" | Layer 4 fires, classification returned, action chips rendered |
| USIM-3 | User types "how many surveys do I have?" | Layer 2 fires, data summary returned |
| USIM-4 | User types "search the web for BS5837" | Layer 1.5 fires, web search runs |
| USIM-5 | User types "what did we just do?" | Layer 3 fires, history returned |
| USIM-6 | User dismisses sheet-full | State → bar |
| USIM-7 | User taps action chip "Open survey X" | Navigate action fires, route changes |
| USIM-8 | User types empty prompt | Returns "Empty prompt" error |
| USIM-9 | User types "Write a blog post about oaks" | Writing prompt detected, clear error returned (smart provider) |
| USIM-10 | User swipes up from bar | State → sheet-full (RT-2.4) |

---

## C7 — Reproducibility Test

**Deterministic guarantee:** For all Smart adapter responses, the same prompt + same history produces the same response.

**Test:** `assistant.reproducibility.test.ts` (planned Phase A2.8)
1. Send `findHeuristic('who are you')` 100 times — same response every time
2. Send `formatAnswer('Test', ['evidence-1'])` with same inputs — same output hash
3. Send `summarise()` twice — same counts (no time-based randomness)

**Non-determinism note:** generative LLM providers (anthropic, openai) are NOT deterministic per-request; C7 only applies to Smart adapter and heuristics library.

---

## C8 — Shape Matrix (per-surface × per-mode × per-chip × per-state)

**Per FVW v8 §30:** C8 captures the shape of the module across its surfaces, modes, chips, and states.

| Surface | Modes | Chips | States |
|---|---|---|---|
| AssistantDock (bar) | view-only | (none — dormant) | bar |
| AssistantDock (sheet-min) | text-only | navigate, compose | sheet-min |
| AssistantDock (sheet-full) | text-only, web-search | navigate, open-item, compose, export | sheet-full |
| AssistantDock (peek) | text-only | navigate, open-item | peek |

**Mode × Chip matrix:**
- text-only mode: navigate, compose, open-item, export all available
- web-search mode (Layer 1.5): navigate, open-item, export; compose disabled during search

**State × Chip matrix:**
- bar: no chips
- sheet-min: navigate + compose (input field)
- sheet-full: navigate + open-item + compose (full input + history) + export
- peek: navigate + open-item only (no input)

**Per-view context:** the surfaces are not per-view (AssistantDock is global); however, the `surface-context` anchor type carries the current view's context into the assistant.

---

## C9 — State Shape Inventory (per v9-fvw proposal §22 C9)

**Every localStorage key, every in-memory variable, every storage shape that affects UI.**

### C9.1 localStorage keys (handled by this module or affected by it)

```
oscar:provider              # LLM provider selection (stub/smart/anthropic/openai/local)
oscar:theme                 # theme (light/dark) — branding, not this module
oscar:brain-downloaded      # brain manifest download state — brain module
oscar:tour-completed        # onboarding completion — onboarding module
```

**This module reads/writes:** `oscar:provider`
**This module reads but does not write:** none
**This module affects UI based on:** `oscar:provider`

### C9.2 In-memory variables (per file)

| File | Variable | Type | Affects UI |
|---|---|---|---|
| `AssistantDock.tsx` | `state` | `DockState` | Sheet rendering |
| `AssistantDock.tsx` | `messages` | `ChatMessage[]` | Bubble rendering |
| `context.tsx` | dock context | object | All sheets |
| `memory.ts` | `__sharedMemory` | `ConversationMemory` | Layer 3 history |
| `ask.ts` | `cachedAdapter`, `cachedAdapterFor` | `LlmAdapter`, `string` | Adapter reuse |

### C9.3 IDB state (this module's read/write)

- **None.** The assistant does NOT directly persist to IDB. It dispatches to repos via action chips.
- **Indirect:** action chips that fire `postsRepo.put()`, `surveysRepo.put()`, etc. trigger IDB writes via other modules.

### C9.4 UI-affecting state summary

This module affects UI based on:
- `oscar:provider` (which LLM is active)
- `state` (4-state dock state)
- `messages` (conversation history)
- `__sharedMemory` (cross-call memory)
- `cachedAdapter` (which adapter instance is reused)

### C9.5 Module-level state holders

| Holder | Scope | Persistence |
|---|---|---|
| React context (`context.tsx`) | App-wide | ephemeral (until reload) |
| `__sharedMemory` in `memory.ts` | Module-level | ephemeral (process-lifetime) |

---

## END OF ASSISTANT CODEX

*Behaviour-first codex for Oscar Assistant module. Per FVW v8 §22 + §30 + v9-fvw C9.*
