# Nucleic — macOS UX & Information Architecture (v0) How the runtime state ([RUNTIME_ARCHITECTURE](RUNTIME_ARCHITECTURE.md)) becomes screens and flows. Altitude: navigation model, the core screens as wireframes, and the few interaction flows that define the product. Not a pixel spec. **North star:** the user is running *many* agents at once. The UI must answer, at a glance, "which session needs me?" and make answering it (approve / unblock / integrate) one action away. --- ## 1. Navigation model Three-column macOS app (`NavigationSplitView`): ``` ┌────────────┬────────────────────────────┬──────────────────────────┐ │ SIDEBAR │ SESSION LIST / DASHBOARD │ SESSION DETAIL │ │ │ │ │ │ ▸ Project A│ ● auth-refactor ⚠ needs │ [Transcript | Diff | Log]│ │ Project B│ ◐ flaky-tests running │ │ │ ▸ Project C│ ✓ docs-pass done │ …active tab… │ │ │ ○ spike-graphql idle │ │ │ ───────── │ │ │ │ Dashboard │ [ + New Session ] │ [approval bar / input] │ │ Devices │ │ │ └────────────┴────────────────────────────┴──────────────────────────┘ ``` - **Sidebar:** projects (expandable to their sessions) + global destinations (Dashboard, Devices, Settings). - **Middle:** the session list for the selected project, or the cross-project **Dashboard**. - **Detail:** the selected session. Status is encoded once, consistently, everywhere a session appears: | Glyph | Status | Meaning | | --- | --- | --- | | `⚠` (amber, pulsing) | `awaitingApproval` | **Needs you now** — blocking on a permission | | `◔` (amber) | `awaitingInput` | Turn done, waiting for your next prompt | | `◐` (blue, animated) | `running` | Agent working | | `✓` (green) | `finished` | Done; ready to integrate | | `○` (gray) | `idle` | Provisioned, not started | | `✕` (red) | `error` | Failed / interrupted | "Needs you" states (`⚠`, `◔`, `✕`) sort to the top and drive badge counts. --- ## 2. Dashboard (the "N agents at once" view) The answer to "what needs me?" across every project. A density-first grid of session cards: ``` ┌─ Needs attention (2) ───────────────────────────────────────────────┐ │ ⚠ auth-refactor ProjA "Run: npm publish" [Deny] [Allow ▸] │ │ ◔ payment-flow ProjC waiting for input [Open] │ ├─ Running (4) ───────────────────────────────────────────────────────┤ │ ◐ flaky-tests ProjA +312/−40 · editing test_runner.py · 2m │ │ ◐ graphql ProjB +88/−12 · running tests · 40s │ │ … │ ├─ Done, ready to integrate (3) ──────────────────────────────────────┤ │ ✓ docs-pass ProjA +120/−5 [Review & Merge ▸] │ └──────────────────────────────────────────────────────────────────────┘ ``` - **Inline approvals**: low-risk approvals can be answered straight from the dashboard without opening the session (high-risk ones require opening detail — see §4). - Each card shows live one-liner activity (current tool / last action) + diffstat + elapsed. - Sectioned by urgency, not by project, so triage is global. --- ## 3. Session detail Header: title, project, backend badge (Claude/Codex), branch, status, controls (`Interrupt`, `Resume`, `⋯`). Body is three tabs over one shared bottom action area. ### 3.1 Transcript tab (default) Renders the canonical transcript stream: - **Assistant prose** — markdown, streaming token-by-token when `verbosity=.full`. - **Thinking/reasoning** — collapsed by default, expandable. - **Tool calls** — a compact row per call (`▸ Bash git status`) that expands to show input + result; file-editing tools link to the Diff tab. Color-coded by `Risk`. - **System/log** markers inline (setup output, retries, interrupts). Auto-scrolls while running; "jump to latest" pill when scrolled up. ### 3.2 Diff tab The live worktree diff vs `base_sha` (WORKTREE_MANAGER §4): - File tree (left) with per-file `+/−`, split/unified toggle, syntax highlight. - Shows uncommitted *and* committed work (so it's accurate even if the agent never commits). - Per-hunk navigation; this is also where the user reviews before integrating. ### 3.3 Log tab Raw stdio / native event tail for debugging an adapter or a stuck session. Power-user surface. ### 3.4 Bottom action area (context-sensitive) - `running` → live status + `Interrupt`. - `awaitingApproval` → the **approval bar** (§4). - `awaitingInput` / `finished` → a prompt composer (send a follow-up turn) **and** an `Integrate ▸` button. --- ## 4. Approval flow (the defining interaction) When a session hits `awaitingApproval`, the approval bar slides in (and a notification fires if the app/session isn't foreground): ``` ┌─ ⚠ Permission requested ──────────────────────────────────────────────┐ │ Bash · execute · destructive │ │ $ rm -rf build/ && npm publish │ │ │ │ [ Deny ] [ Allow once ] [ Allow always ▾ ] (answered by you) │ │ ├ this command, this session │ │ ├ any Bash, this session │ │ └ Bash matching `npm *`, this session │ └───────────────────────────────────────────────────────────────────────┘ ``` - **Risk-aware emphasis:** `destructive`/`network` requests are visually loud and *cannot* be answered from the dashboard — they require opening detail (deliberate friction). - **`Allow always`** writes an `always_rule` (RUNTIME §5) at the chosen scope; future matches auto-resolve silently. - **Modify-and-allow** (Claude only; `canModifyToolInput`) reveals an editable input field. Hidden for backends without the capability — the UI reads `BackendCapabilities`. - **Multi-client:** if the iPhone answers first, the bar collapses with "Approved on iPhone" (first-responder-wins, SYNC §5.4). - **Non-interactive (Codex `exec`)**: instead of an approval bar, a banner — "This session runs under a fixed policy; blocked actions fail automatically" — set at creation. --- ## 5. New-session flow `+ New Session` sheet: ``` Project: [ ProjA ▾ ] Base branch: [ main ▾ ] Backend: ( ● Claude ○ Codex ) Model: [ default ▾ ] Approvals: ( ● Interactive ○ Fixed policy ) [Codex: Sandbox ▾] Title: [ auth-refactor ] → branch nucleic/auth-refactor Prompt: ┌─────────────────────────────────────────────┐ │ Refactor the auth layer to… │ └─────────────────────────────────────────────┘ [ Cancel ] [ Start ] ``` On `Start`: provision worktree → run setup script (output streams into a fresh session detail view as it provisions) → backend starts. Capability-driven fields (Sandbox only for Codex; "Fixed policy" warns it disables interactive approvals). --- ## 6. Integration flow From `awaitingInput`/`finished` → `Integrate ▸`: ``` Review diff (Diff tab) → choose: ◦ Strategy: ( ● Merge ○ Rebase ○ Squash ) ◦ Into: [ main ▾ ] ◦ Commit pending work: [auto message: "auth-refactor"] (editable) —or— [ Open Pull Request ▸ ] [ Discard… ] ``` - **Clean** → success toast, session → `finished/merged`, offer to clean up the worktree. - **Conflict** → switches to a conflict view listing conflicted paths with two outs: *Resolve in worktree* (optionally **"Ask the agent to resolve"** → spins a new turn) or *Abort* (WORKTREE_MANAGER §6.2). - **Discard** → confirm if unmerged/dirty, then remove worktree + branch. --- ## 7. Devices & ambient surfaces - **Devices screen:** paired iPhones (label, scope, last seen), pairing via a displayed QR (SYNC §4.2), and **revoke**. - **Menu-bar item:** count of "needs you" sessions; click → mini list jumping into approvals. - **Notifications:** on `awaitingApproval`/`awaitingInput`; actionable (Allow/Deny) for low-risk; clicking opens the session. - **Dock badge:** total attention count. --- ## 8. Mapping to the runtime | UI surface | Source | Intent it sends | | --- | --- | --- | | Session list / dashboard | `AppStore` session summaries (`@Observable`) | select, inline-approve | | Transcript tab | scoped event stream from `SessionController` | scroll only (read) | | Diff tab | `WorktreeManager.diff` (on demand, debounced) | — | | Approval bar | `approval` queue + `BackendCapabilities` | `respondToApproval(id, Decision)` | | Prompt composer | — | `sendInput(AgentInput)` | | Integrate sheet | `WorktreeManager.status` | `requestIntegrate(strategy, target, commit)` | UI holds **no canonical state** — it observes the `SessionController` and sends intents (RUNTIME §3). This keeps Mac and iPhone as two renderers of the same authority. --- ## 9. Open questions 1. **Inline vs. detail-only approvals** — exact risk threshold that forces opening detail (proposed: `destructive`/`network` → detail; `readOnly`/`write`/`execute` → inline-OK). 2. **Transcript rendering budget** — virtualization for very long sessions; how much history to keep mounted vs. page in (ties RUNTIME open-Q #3). 3. **Diff refresh cadence** — on every `fileChange` event vs. on turn boundaries vs. manual, balancing liveness against churn on big diffs. 4. **Density** — is a grid dashboard enough at 20+ sessions, or do we need a compact "mission control" list mode and saved filters?