11 KiB
Nucleic — macOS UX & Information Architecture (v0)
How the runtime state (RUNTIME_ARCHITECTURE) 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 byRisk. - 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 anIntegrate ▸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 · network │
│ $ 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/networkrequests are visually loud and cannot be answered from the dashboard — they require opening detail (deliberate friction). Allow alwayswrites analways_rule(RUNTIME §5) at the chosen scope; future matches auto-resolve silently. Not offered ondestructiverequests (rm, force-push,reset --hard, …): each must be a deliberate, one-offAllow, and a destructive call is never satisfied by a pre-existing broader rule either — theApprovalCoordinatorre-surfaces it.- Modify-and-allow (Claude only;
canModifyToolInput) reveals an editable input field. Hidden for backends without the capability — the UI readsBackendCapabilities. - 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
- Inline vs. detail-only approvals — exact risk threshold that forces opening detail
(proposed:
destructive/network→ detail;readOnly/write/execute→ inline-OK). - Transcript rendering budget — virtualization for very long sessions; how much history to keep mounted vs. page in (ties RUNTIME open-Q #3).
- Diff refresh cadence — on every
fileChangeevent vs. on turn boundaries vs. manual, balancing liveness against churn on big diffs. - Density — is a grid dashboard enough at 20+ sessions, or do we need a compact "mission control" list mode and saved filters?