# Oscar Platform — Ambient AI (Phase 8)

**Status:** draft, operator-approved (2026-07-02 18:02 UTC)
**Replaces:** SheetMin chat panel pattern (Phase 7)
**FVW tier:** §35 (Contextual Surfaces) + §40 (Multimodal Capture)

## Why we're changing

The chat-panel pattern (SheetMin / SheetFull) is wrong for mobile. Three
reasons we now have evidence for:

1. **It hides the user's work.** When the chat panel opens, the page content
   scrolls behind it. On a 360x640 phone viewport, the panel covers 55% of
   the screen. The user can't see what Oscar is talking about.
2. **It requires a tap to dismiss.** Mobile users hit × but the state machine
   in v8 had `tap-collapse` going to a background-nav icon, not the bar.
   They thought the close button was broken. It was — the seam was wrong.
3. **It splits attention.** The user has two screens (page + chat) when
   they need one. Mobile users don't have the screen space for two surfaces
   running at once.

The right pattern for mobile is **ambient AI**: the input bar is the
chat, the toasts are the responses, the form is the canvas. The user
never leaves their current view.

## The new architecture

### Surfaces

| Surface | Purpose | When visible |
|---|---|---|
| **Input bar** (bottom, always present) | Type a question, send, see live progress | Always |
| **Status pill** (above the input bar) | Live progress text + elapsed seconds + pulsing dots | Only during a long op (rewrite, classify, draft) |
| **Toast** (top, slide-in) | Completed AI work, errors, suggestions | When an event fires |
| **Inline suggestion** (in the form, near the relevant field) | "Tap to apply" for AI suggestions on the current field | When the AI has a suggestion for the field the user is on |
| **Conversation drawer** (right side, slides in) | Recent Q&A history, scrollable | Only on desktop, or when the user explicitly taps "history" |

The mobile user **never sees a chat panel**. They see:
- The page they're on
- A bar at the bottom that grows text when something is happening
- Toasts that appear at the top and dismiss with a swipe

The desktop user gets the same surfaces plus the right-side conversation
drawer for keyboard-heavy workflows.

### The input bar's three modes

The input bar is a state machine. Three modes:

```
IDLE
  User types → DRAFTING
  Send → SENDING (transitions to ACTIVE on first progress event)
  No activity for 30s → back to IDLE

DRAFTING
  User typing text in the bar
  Send button enabled when text is non-empty
  Esc → back to IDLE (clears draft)

ACTIVE (long-running op in flight)
  Input bar shrinks to a status row showing:
    "[icon] [progress text]  [elapsed]s  [× cancel]"
  Tap bar → expands to full input (user can ask a follow-up question
    while the original op continues in the background)
  × → cancels the in-flight op, returns to IDLE
  Done → bar returns to IDLE, toast appears at the top with the result
```

### Context propagation

The AI needs to know:
- Which page the user is on (`/notes/123`, `/surveys/abc`, etc.)
- Which form they're in (the `draft` state of the Blog/Survey/whatever)
- What's selected (if the user highlighted text in the body)
- Recent history (last 6 turns)

The `AssistantDock` already knows the current page via `useLocation()`.
The current view passes its `draft` to a `useAssistantContext` hook:

```ts
// In any view:
useAssistantContext({
  page: '/notes/123',
  summary: 'Blog post about the beach trip',
  selectedText: editorSelection, // optional
  draft: draft, // the form state, optional
});
```

This context is included in every `askOscar` call as a system-prompt
prefix. The AI can then refer to "your current blog post" or "the
tree you just selected."

### Apply-pattern for destructive AI actions

**The AI never overwrites the user's text without explicit consent.**

For each kind of long-running AI work:

| Action | What the AI does | What the user sees |
|---|---|---|
| Rewrite notes body | Streams the rewrite into a `rewriteDraft` | Status pill: "Rewriting… 12s". Original stays intact. Toast on done: "Rewrite ready — [Preview] [Apply] [Discard]". User picks one. |
| Classify a tree | Streams a category + reasoning | Inline suggestion next to the Category field: "A — Apply / ×". One tap applies. |
| Draft an outline | Streams a bulleted outline | Toast: "Outline drafted (7 sections) — [Insert] [Edit] [Discard]" |
| Suggest a title | Returns 5 options | Toast with 5 buttons, one per title. One tap replaces. |
| Pre-warm LLM | Loads model into RAM | One-time toast on first launch: "Oscar is loading the model — first reply takes ~10s" |
| Cold start on user input | Loads model after first request | Input bar shows "Loading Oscar… 8s" until first token arrives |

The original content is **always preserved** until the user explicitly
accepts. No accidental overwrites. Undo is always available.

### Toast system

Three toast types:

1. **Result toast** (success). Slides in from top, 5s default timeout.
   Has 1-3 action buttons. Dismisses on swipe-up or ×.
   Example: "Rewrite ready — [Preview] [Apply] [×]"

2. **Status toast** (info). Slides in from top, 3s timeout, no buttons.
   Example: "Brain downloaded (398 MB) — ready offline"

3. **Error toast** (warning). Slides in from top, persistent until ×.
   Example: "Couldn't reach Oscar — your text is preserved. [Retry] [×]"

Toasts stack vertically. Max 3 visible at once. Older ones fade.

### Conversation drawer (desktop only)

A right-side panel that shows the last N turns of Q&A. Triggered by
tapping the input bar's avatar when not in ACTIVE mode. Hidden on
mobile entirely. The user on desktop has the screen space for it.

## What changes in the code

### Files to add

- `src/assistant/ambient.ts` — the AmbientContext, useAssistantContext hook
- `src/assistant/InputBar.tsx` — replaces Bar.tsx (the 3-mode state machine)
- `src/assistant/StatusPill.tsx` — the live progress display above the bar
- `src/assistant/ResultToast.tsx` — the apply-pattern result toast
- `src/assistant/ConversationDrawer.tsx` — desktop-only history

### Files to modify

- `src/assistant/AssistantDock.tsx` — replace SheetMin render branch with
  InputBar + StatusPill + ResultToast host
- `src/views/Blog.tsx` — use the ambient pattern; surface rewrites as
  ResultToasts, not as live-streamed text in the body
- `src/views/Settings.tsx` — toasts for brain download, model load, etc.
- `src/views/Surveys.tsx` — toast-based species suggestions
- `src/main.tsx` — mount the ResultToast host at the root

### Files to delete

- `src/assistant/SheetMin.tsx` — no longer needed on mobile
- `src/assistant/SheetFull.tsx` — no longer needed on mobile
- `src/assistant/BackgroundNav.tsx` — no longer needed (the dead
  state-machine terminal state)
- `src/assistant/state-machine.ts` — replaced by simpler per-surface
  state machines in each component

### Pact updates

- `pact/dna/copilot-style.md` — add ambient-AI patterns to the style guide
- `pact/llm-tier-research.md` — add Phase 2 (ambient) lessons
- `pact/decisions/` — new file `D-2026-07-02-ambient-ai.md` recording
  the decision to replace the chat panel pattern

## Open questions

1. **Should the ConversationDrawer be a separate route** (`/chat`)? Or
   always-on right panel? Decision: right panel for desktop; mobile
   users get a `/chat` route only as an explicit escape hatch.
2. **How many turns of history does the bar remember?** 6? 10? More
   requires more tokens per request. Decision: 6.
3. **Should we save the AI's draft rewrives** (so the user can re-apply
   them after closing the app)? Yes — to IDB, keyed by `notes/{id}/rewrite`.
4. **Cancellation**: do we send a real cancel signal to Ollama, or just
   abandon the request? For 1.7B, the request finishes in seconds anyway.
   Decision: abandon. Document that cancellation is "stop waiting", not
   "stop the model".

## FVW transferable lessons

These patterns are reusable across any FVW v8 app (FreshVibe Studio,
FreshCards, FvRE, etc.):

1. **Ambient AI over modal chat panels on mobile.** Any AI surface in an
   FVW app should default to the ambient pattern. The chat panel is a
   desktop affordance that doesn't survive the mobile viewport.

2. **The Apply pattern for destructive AI actions.** AI suggestions are
   always drafts until the user taps Apply. Originals are preserved
   until then. This is the same pattern as Apple Intelligence's
   "Rewrite Suggestions" — proven to be the right trust model.

3. **Status text reads faster than spinners.** "Looking up BS5837 §5.2…"
   is more informative than a pulsing dot. Users actually read it,
   which makes the wait feel shorter (per operator feedback 2026-07-02).

4. **Pre-warm on boot, not on first request.** The 9-10s cold start is
   hidden if the request fires during page mount. Pattern: fire a
   no-op request to the LLM as the first effect in `<App />`.

5. **Streaming tokens over faster backends.** A 25s response that streams
   tokens feels faster than a 10s response that shows a spinner. Always
   prefer streaming, even if it means a longer total wait.

6. **Mark small-model outputs as drafts.** A pill that says "draft" sets
   the right expectation. Cascade escalation is explicit, not automatic.

7. **VPS self-hosted inference** is a real option for small apps.
   Sovereign. Cheap. 1.8 GiB RAM + 4 GB swap fits a 1.7B Q4_K_M model
   with room to spare. Cold-start is 9-10s; warm is 5-10s/reply.

## Acceptance criteria

- [ ] Input bar shows live progress during long ops (Connecting →
      Reading → Thinking → Composing) with elapsed seconds
- [ ] User can type a follow-up question while a long op runs
- [ ] × on the status pill cancels the in-flight request and restores
      the input bar to idle
- [ ] AI never overwrites the user's text without their explicit consent
- [ ] Toasts stack vertically, max 3 visible, swipe-up to dismiss
- [ ] On mobile (< 768px), the conversation drawer is not rendered
- [ ] Brain download (if user opts in) shows progress in a status toast
- [ ] All 222 existing tests still pass
- [ ] New tests for: ambient context propagation, status pill state
      transitions, toast apply/discard actions
- [ ] On a 360x640 viewport, the chat panel never covers more than 25%
      of the screen height

## Out of scope (later phases)

- In-browser 0.5B brain (Phase 9)
- Cascade escalation to 3B (Phase 10)
- Voice input via Web Speech API (Phase 11)
- Conversation history export (Phase 12)
