# Native UX Roadmap

> Canonical path: `specs/desktop/native-ux-roadmap.md`

This is the TODO tracker for native desktop optimizations that were intentionally
deferred during the initial runtime/shell refactor. Each item carries the
conversation context that decided its priority so a future implementer doesn't
have to re-derive it.

Status legend:
- **Done** — shipped.
- **In progress** — currently being worked.
- **Queued** — next up after the desktop client runs end-to-end.
- **Deferred** — explicit decision to postpone; rationale recorded.

The single gating priority for the moment is: *first ensure the Desktop client
runs end-to-end, then pick items off this list in priority order.*

---

## Done

### Runtime contract as the single convergence point
- **What**: The browser/desktop divergence lives in `web/src/runtime/contract.tsx`
  (capability flags + entry signals) and the two top-level shells
  (`BrowserRoot.tsx`, `DesktopRoot.tsx`). Shared product code consumes the
  runtime; no `variant` prop proliferation.
- **Why**: User rejected the alternative of sprinkling `variant="desktop"` props
  through shared components and asked to converge differences into a narrow zone.

### Server-selection stage (desktop-only)
- **What**: `DesktopRoot` shows `DesktopOnboardingPage` until `runtime.activeServer`
  is set, then renders the shared `AgentTeamApp`. Browser shell never sees this.
- **Why**: Desktop is a multi-server client (multiple AgentTeam deployments); browser
  is single-server per origin. The selection surface makes no sense on web.

### MemoryRouter on desktop (no URL persistence)
- **What**: `BrowserRoot` wraps with `BrowserRouter`; `DesktopRoot` wraps with
  `MemoryRouter`. Router ownership moved out of `bootstrap/renderApp.tsx`.
- **Why**: Desktop has no address bar — URL persistence buys nothing, and the
  back/forward buttons of the OS don't translate to in-app routes. MemoryRouter
  keeps the same `useLocation` / `useNavigate` hooks shared code uses.

### Electron lifecycle, deep linking, and window state
- **What**: Electron owns the single-instance lock, retained window, tray,
  `agentteam://` registration/queueing, window layout state, and native quit
  lifecycle on macOS and Windows.
- **Why**: Native lifecycle has one owner while the Go daemon stays headless.
- **Where**: `desktop/electron/src/main/`.

---

## Deferred

These are explicitly postponed per the user's call. Don't pick these up unless
the user re-prioritizes them.

### Native dialogs (replace web modals with OS dialogs)
- **Status**: Deferred.
- **Why**: User wants UI consistency between web and desktop; native dialogs
  look different from the in-app modal system and would create visual jitter.
- **How to apply**: Keep using the existing web modal components. Re-open this
  decision only if a specific modal is demonstrably worse as a web component
  (e.g. file picker semantics).

### System notifications
- **Status**: Deferred.
- **Why**: User wants to ship the core native shell first; notification
  plumbing (permission prompts, focus mode, notification center integration)
  can be layered on later without rearchitecting anything.
- **How to apply**: When picked up, expose a new `notifications` capability on
  the runtime contract so web stays untouched.

### Drag-and-drop files into the composer
- **Status**: Deferred.
- **Why**: Deferred for the same reason as system notifications — focus on
  getting the shell running first. The web composer already handles paste and
  file picker; drag-and-drop is a power-user convenience.
- **How to apply**: Wire via a typed Electron preload capability that forwards OS drop
  events to the existing attachment handlers in the shared composer.

---

## Queued

Candidates for after the desktop client runs end-to-end. Order is rough
priority based on user-perceived native-ness × implementation cost.

### 1. Native context menus + spell-check
- **What**: Replace HTML right-click menus with native OS menus; enable
  spell-check on text inputs via the webview's native spell-checker.
- **Why**: Web context menus stand out as un-native. Spell-check is free once
  the webview exposes it.
- **Approx. effort**: Medium (mostly testing across the three platforms).

### 2. Cmd+K / Ctrl+K command palette
- **What**: Quick-switcher for workspaces, channels, DMs, threads.
- **Why**: Power-user feature that immediately makes the app feel native.
  Slack / Linear / Notion all have this.
- **Approx. effort**: Medium. UI is shared code; integration is just a
  keyboard shortcut handler in `DesktopRoot`.

### 3. Auto-launch at login
- **What**: System tray / settings toggle for "Start AgentTeam when I log in".
- **Why**: Common desktop-app expectation. Use Electron login-item settings on
  macOS/Windows and the standard `.desktop` autostart entry on Linux.
- **Approx. effort**: Small per-platform; cross-platform wrapper is the work.

### 4. Background throttling
- **What**: When the window is hidden, throttle or pause expensive work
  (polling, animations, realtime sockets) to save battery.
- **Why**: Webcode currently polls on `visibilitychange`, but desktop's
  visibility model is different (window-hidden vs. app-hidden).
- **Approx. effort**: Small. Hook into Electron window events and feed them as
  `visibilitychange` to the existing handlers.

### 5. Biometric unlock (Touch ID / Windows Hello)
- **What**: Require biometric unlock after the app has been backgrounded for
  N minutes.
- **Why**: Defense in depth for users who share the machine.
- **Approx. effort**: Medium. Crypto lives in the OS; integration is mostly
  UX (timeout policy, fallback password).

---

## Notes for future implementers

- **Where new native features plug in**: add a capability to
  `web/src/runtime/contract.tsx` (browser returns a stub, desktop returns the
  real impl). Do **not** add `variant` props to shared components.
- **Desktop-only UI** (e.g. `DesktopOnboardingPage`) lives under
  `web/src/desktop/`. It's loaded via the desktop shell and never reaches the
  browser bundle.
- **Server-side / daemon-side hooks** live under `desktop/internal/` and are
  pure Go — no web import path should ever reach into them.
- **MemoryRouter has no URL**: anything that currently inspects
  `window.location` for navigation state needs to be re-routed through the
  runtime contract's `readEntrySignals` (which on desktop returns the empty
  set). New "URL-derived" state should follow the same pattern.
