diff --git a/codeburn-desktop-wireframes.html b/codeburn-desktop-wireframes.html
new file mode 100644
index 00000000..bebb5d6e
--- /dev/null
+++ b/codeburn-desktop-wireframes.html
@@ -0,0 +1,731 @@
+
CodeBurn Desktop — Wireframes
+
+
+
+
+
CodeBurn Desktop — indigo instrument
+
Wireframes v6. Cool near-black system with clean gradients: indigo→violet as the single UI gradient, blue/purple/lavender/cyan as the model-series palette, capsule gradient bars, and a Sankey cost-flow as the premium signature. Machined base kept: hairlines, mono digits, keycaps, scope labels.
+
v6.1 · 2026-07-10 · references: Dash0 AI-coding dashboard (series palette, card headers, Sankey), gradient-capsule chart, ambient glow · 30-day default period (7 bars never balloon again) · shiny orange CodeBurn brand mark · full period vocabulary: Today / 7D / 30D / Month / 6M / Custom
+
+
+
+
Design language
+
One gradient for the UI (indigo→violet: active rail, primary button, plan tracks, toggle). Data gets the series palette. Trouble is text color only — amber pace, red overage, mint savings.
+
+
Canvas#0B0D13 + ambient glow
+
Panel#12151F / head #161A27
+
UI gradient#5B8CFF → #8B7CF6
+
Opus#5B8CFF
+
Sonnet#8B7CF6
+
Haiku#B5A8FF
+
GPT / Codex#4DD8E6
+
Savings#3ECF8E
+
Pace#E8B93E
+
Overage#F26D6D
+
+
+
One UI gradientIndigo→violet marks what's interactive or active. It never colors data.
+
Series palette for dataEach model owns a hue everywhere — chart, legend, Sankey, table dot. Recognition over decoration.
+
Capsule barsHistory in muted slate capsules; the emphasized bar gets the blue→cyan gradient and a soft glow.
+
Card = header + bodyDash0-style header strip carries the title and controls, keeping bodies pure content.
+
Status is textAmber pace, red overage, mint savings. No dots, no pills, no gradient alarms.
+
+
+
+
+
+
01 · Overview
+
Ten-second triage. Big clean numbers, one glowing capsule chart, the damage list.
+
+
+
+
+
+
Overview
+ Last 30 days · All providers · 2 devices
+
+
Today7D30DMonth6MCustom
+
All providers
+
+
+
+
Today
$6.20
12 sessions
+
Month to date
$312.40
+24% vs June pace
+
Projected month
$968 est
$748 over plans
+
Waste found
$23.60 /wk
3 fixes ready
+
+
+
Daily spendbiggest driver: Opus 4.8 in codeburn, +61% — why ›
Notes. Capsule bars: slate history, gradient + glow on the peak, plain blue on second-highest — the eye finds Monday instantly. Session rows carry the model's series dot. Deltas: blue = neutral info, amber = pace, red = overage, mint = good.
+
+
+
+
+
02 · Spend
+
Stacked series by model, then the signature: cost flowing model → project as a Sankey. Every ribbon is a filterable claim.
Cost flow · model → projectclick a ribbon to filter
+
+
+
+
+
+
⌘KCommand⌘EExport view⇥Switch lens
+
+
+
+
Notes. The Sankey is the premium signature — each ribbon inherits its model's hue and fades toward the project side; clicking one scopes every panel to that model × project pair. Stacked bars share the same series palette, so the legend teaches colors once for the whole app.
+
+
+
+
+
03 · Optimize
+
Waste, reverts, abandoned, fixes. Findings ranked by recoverable dollars, evidence in mono, savings in mint.
+ Twelve sessions went nowhere
+ $39.20 across 12 abandoned sessions with no surviving edits — nearly all started without a stated goal.
+
no surviving edits vs git · 12 sessions › · full list under Abandoned
+
+ $5.80/wk
+
+
+
+
+
⌘CCopy fix→Open sessions
+
+
+
+
Notes. The old Yield analysis lives under Reverts and Abandoned (git-correlated, per codeburn yield). Segments carry their dollar totals so the tab row itself is a summary.
+
+
+
+
+
04 · Models
+
The pricing table with each model's series dot. Compare is a mode; unpriced rows carry their own fix.
+
+
+
+
+
+
Models
+ Last 30 days · All providers
+
+
By modelBy task
+ Compare…
+
+
+
+
+
+
Model
Calls
Input
Output
Cache read
Cost
Saved
+
+
Claude Opus 4.8
4,812
152.6M
9.64M
119.4M
$331.20
$86.40
+
GPT-5.5 Codex
2,704
86.9M
7.52M
45.1M
$137.90
$35.10
+
Claude Sonnet 5
3,318
77.7M
6.08M
63.3M
$108.63
$27.90
+
Claude Haiku 4.5
1,270
32.5M
2.84M
16.9M
$34.75
$5.85
+
my-proxy-model add alias ›
176
4.8M
0.4M
—
$0.00
—
+
+
+
+
+
+
⌘EExport tableSpaceSelect for comparefriendly names · raw IDs on hover
+
+
+
+
Notes. Series dots make the table, charts, and Sankey one continuous vocabulary. By-task nests category rows under each model (models --by-task); Compare… slides a sheet over the table for the selected rows.
+
+
+
+
+
05 · Plans
+
Vendor clocks, not analytics periods. The gradient track is UI; the warning is text.
+
+
+
+
+
+
Plans
+ Cycle Jun 15 – Jul 14 · day 26 of 30
+
+
Cycle: Jun 15 – Jul 14
+ Add plan…
+
+
+
+
Claude Max$200 / month · claude$186.20 · 93%
+
+
On pace to exceed in 4 days — projected $214 by Jul 14
+
+
+
Cursor Pro$20 / month · cursor$8.20 · 41%
+
+
On track
+
+
+
API usagecodex · pay as you go, no plan$31.02 this cycle
+
+
+
+
↵Edit planpresets: Claude Max/Pro · Cursor Pro · custom
+
+
+
+
Notes. One overage line per provider plan, mirroring the CLI. Past 100% the fill switches to the red gradient and the overage renders in dollars beside it.
+
+
+
+
+
06 · Settings — with Devices inside
+
Own rail, deep-linkable. Pairing is the only amber on screen; Approve is the view's single gradient button.
+
+
+
+
+
Settings
+
+
+
+
+
This device
+
Toruk's MacBook ProVisible on the local network as codeburn-mbp.local
Visibility: on
+
+
+
Discovered nearbylistening…
+
Mac Studiowants to pair · fingerprint 7F:2A:…:C4
Approve
+
+
+
Paired
+
+
toruk-minilast pull 2h ago · 34 sessions · $41.20 this month
Pull now
+
Combine usage from paired devicesscope captions gain “· 2 devices” when on
+
+
+
+
+
escBackpairing uses mutual TLS · approve-style, no PIN
+
+
+
+
Notes. No period chrome — configuration is timeless. Approve-style pairing mirrors the CLI: a human verifies the fingerprint, no PIN.
+
+
+
+
+
07 · States & trust
+
Missing tools, denied permissions, empty data are normal for local-first scanning. Text status only.
+
+
+
Empty / permission
+
Claude1,412 sessions
+
Codex688 sessions
+
Cursorpermission denied — grant Full Disk Access
Fix…
+
Devinnot installed
+
+
+
Loading
+ Scanning 30 providers…
+
First scan reads every session file; later scans are incremental via the daily cache.
+
+
+
+
+
+
Provenance
+ $31.02 est
+
Cursor reports messages without token counts; cost is estimated from message sizes and LiteLLM prices.
+ Inactive window: gradient rail and selection dim to slate.
+ Focus: indigo focus rings, never browser outlines.
+ Hover: row fills at 5% lavender; chart hover = column highlight + mono value popover; Sankey ribbons brighten on hover and filter on click.
+ Scrolling: native overlay scrollbars.
+ ⌘K palette: filters, export, view jumps, session search.
+ Series rule: each model's hue is global — chart, Sankey, dots, legends. UI gradient never colors data.
+ Light theme: same ladder on paper (#FAFBFE, panels white, hairlines 6% indigo-black) — implementation deliverable, same tokens.
+ Export: ⌘E everywhere, scoped to current filters.
+ Motion (codex): bars rise on view entry, Sankey ribbons draw left→right, active rail glides — 120–180ms, no bounce.
+ Depth (codex): cards carry a 1px top-edge light and faint bottom occlusion; active controls get inner highlight + subtle bloom, centers stay dark; 1–2% canvas noise to kill flat Electron darkness.
+ Rendering (codex): charts locked to device pixel ratio, fixed bar geometry, legends exactly matching series colors — test at 1×/2×.
+ Numerals: aligned decimals, muted currency symbols, deltas enlarged only when meaningful.
+ Period control: all six options shown per product decision; codex's alternative (segmented Today·7D·30D·Month + “More ▾” holding 6M/Custom) is on file if the toolbar ever feels crowded.
+ Orange discipline (codex): the flame mark stays the single warm object — never data, progress, status, nav, or chart highlights.
+
+
+
+
+
diff --git a/docs/design/codeburn-desktop.md b/docs/design/codeburn-desktop.md
new file mode 100644
index 00000000..8db2ac8f
--- /dev/null
+++ b/docs/design/codeburn-desktop.md
@@ -0,0 +1,182 @@
+# CodeBurn Desktop — Design Spec
+
+**Date:** 2026-07-10
+**Branch:** `feat/desktop-app` (cut from `origin/main`, v0.9.15)
+**Design reference:** `codeburn-desktop-wireframes.html` (v6.1 "indigo instrument", repo root)
+**Status:** Approved design → implementation planning
+
+---
+
+## 1. Summary
+
+Build **CodeBurn Desktop** — a standalone, resizable native desktop application (Electron) that
+renders the approved v6 "indigo instrument" wireframes. It is a first-class app in the vein of
+Claude Desktop / the Codex app, **distinct from**:
+
+- the `dash/` **web dashboard** (React app served by `codeburn web`) — untouched, unrelated;
+- the `mac/` + `gnome/` **menubar** apps — small tray popovers.
+
+The desktop app is local-first: it never sends data anywhere, and it does **not** re-implement
+usage analytics. All aggregation stays in the `codeburn` CLI; the app is a view layer that spawns
+the CLI and renders its JSON — the same contract the menubar apps use today.
+
+## 2. Goals / Non-goals
+
+**Goals**
+- A resizable window with six sections: **Overview · Spend · Optimize · Models · Plans · Settings/Devices**.
+- Pixel-faithful to the approved wireframe (the wireframe CSS is the design system, ported verbatim).
+- Data via the **menubar contract**: spawn `codeburn … --json`, decode, poll on a 30s timer + on demand.
+- Reuse existing CLI JSON where it exists; add small new `--json` emitters where the wireframe needs
+ data that the CLI doesn't expose yet. **No faked/stubbed data in the renderer.**
+
+**Non-goals (this milestone)**
+- Signed/notarized distributable (`.dmg`/`.app`/installer). Milestone 1 runs locally (`electron .`);
+ packaging (electron-builder + notarization) is an explicit follow-up.
+- Touching `dash/`, `mac/`, `gnome/`, or the existing Tauri popover.
+- Auto-update, telemetry, cloud sync, multi-window.
+
+## 3. Architecture
+
+New top-level directory `app/` in the monorepo:
+
+```
+app/
+├── package.json # own package: electron, vite, react 19, typescript
+├── electron/
+│ ├── main.ts # BrowserWindow lifecycle, 30s poll timer, IPC handlers
+│ ├── cli.ts # resolve codeburn binary path + spawn `codeburn … --json` (argv, no shell)
+│ └── preload.ts # contextBridge → window.codeburn.{getOverview,getModels,getPlans,getDevices,…}
+├── renderer/
+│ ├── index.html
+│ ├── main.tsx
+│ ├── App.tsx # sidebar nav + window chrome (period/provider), section router (local state)
+│ ├── styles/
+│ │ └── indigo.css # wireframe CSS ported verbatim (tokens + component classes)
+│ ├── sections/
+│ │ ├── Overview.tsx Spend.tsx Optimize.tsx Models.tsx Plans.tsx Settings.tsx
+│ ├── components/ # shared: Window chrome, Panel, StatCard, CapsuleChart, StackedBars,
+│ │ # Sankey, ListRow, Track, SegTabs, etc. (extracted from wireframe)
+│ ├── lib/
+│ │ ├── ipc.ts # typed wrappers over window.codeburn.*
+│ │ └── types.ts # TS types mirroring each CLI JSON payload
+│ └── hooks/ # useOverview(period), useModels(), … (poll + cache)
+└── vite.config.ts
+```
+
+**Process model**
+- **Main process** is the only place that touches the CLI or the filesystem. It:
+ - resolves the `codeburn` binary path (persisted-path file → `PATH` fallback across brew/nvm/volta/asdf,
+ mirroring `mac/Sources/CodeBurnMenubar/Security/CodeburnCLI.swift`);
+ - spawns `codeburn --json …` as plain argv (no shell), with a hard timeout (~45s) and a
+ cap on concurrent spawns;
+ - exposes IPC handlers per data need; re-polls every 30s and on user action (period/provider change,
+ manual refresh);
+ - handles the "CLI not found / non-zero exit / bad JSON" states → surfaces the wireframe's empty /
+ permission-denied states (§7 "States & trust").
+- **Renderer** runs with `contextIsolation: true`, `nodeIntegration: false`. It receives typed data over
+ IPC and renders. No Node, no direct spawn.
+- **preload** exposes a minimal, typed `window.codeburn` surface via `contextBridge`.
+
+**Binary resolution** reuses the menubar approach; if `codeburn` cannot be found, the app shows a
+first-run "point me at your codeburn CLI" state rather than crashing.
+
+## 4. Data contract (the menubar pattern, generalized)
+
+The app spawns the CLI per section and decodes JSON. Aggregation is 100% CLI-side.
+
+| Section | CLI invocation | Payload | Exists today? |
+|---|---|---|---|
+| Overview | `codeburn status --format menubar-json --period 30days [--provider X]` | `MenubarPayload` (`src/menubar-json.ts`) | ✅ |
+| Spend (bars, projects) | same `MenubarPayload` (`current.topProjects`, `history.daily`) | — | ✅ (⚠ per-model-per-day may need `history.daily` enrichment) |
+| Spend (Sankey model→project) | **new** `codeburn spend --format flow-json` (or extend menubar payload) | model×project cost matrix | ⚠ **new emitter** |
+| Optimize (waste) | `MenubarPayload.optimize` | findings | ✅ |
+| Optimize (reverts/abandoned) | **new** `codeburn yield --json` | shipped-vs-reverted, abandoned sessions | ⚠ **new emitter** |
+| Models | `codeburn models --json` | per-model table (calls/input/output/cache/cost/saved) | ✅ |
+| Plans | **new** `codeburn plan --json` (list plans + usage vs cycle) | plans + progress | ⚠ **new emitter** (data exists in `src/config.ts`; needs JSON shape) |
+| Settings/Devices | `codeburn devices --json` / `codeburn share status --json` (or the `origin/main` `/api/devices`,`/api/share/status` handlers as reference) | this device, discovered, paired | ✅ commands exist on main; ⚠ confirm/add `--json` |
+
+**Rule:** every ⚠ becomes its own CLI-side task in `src/` (TypeScript). The renderer never invents data;
+if an emitter isn't ready, its section shows the wireframe's honest loading/empty state, not fake numbers.
+
+`renderer/lib/types.ts` mirrors each payload as a hand-kept TS type (same convention `dash/` uses — no
+shared type package). Where a menubar payload type already exists in `src/menubar-json.ts`, copy it.
+
+## 5. Styling
+
+Port the wireframe's hand-written CSS **verbatim** into `renderer/styles/indigo.css`:
+- token block (`--pg / --panel / --blue / --purple / --lav / --cyan / --grad / --grad-bar / …`);
+- component classes (`.win / .sb / .ni / .bar / .panel / .phead / .stat / .plot / .bars / .sbars /
+ .li / .track / .seg / .pop / .btn / .rail / .mini / …`).
+
+Each wireframe section (`` blocks in the HTML) maps near-directly to a React component. Shared
+repeating structures (`.panel`, `.stat`, capsule chart, stacked bars, the Sankey SVG, list rows, plan
+tracks) get extracted into `renderer/components/` so the six sections compose them rather than duplicate
+markup. This keeps the approved look exactly while giving each section a clean, independently-testable
+boundary. **Not** rebuilding in Tailwind — that would risk drift from the approved design and cost time.
+
+Light theme is a follow-up (wireframe notes a light ladder using the same tokens); ship dark first.
+
+## 6. Section specs (what each renders)
+
+1. **Overview** — 4 stat cards (Today, Month-to-date, Projected month, Waste found), the daily-spend
+ capsule chart (30 bars, gradient+glow on peak), "Most expensive sessions" list. Source: `MenubarPayload`
+ (`current`, `history.daily`, `topSessions`, `optimize.savingsUSD`); projected month computed from history.
+2. **Spend** — lens tabs (Projects/Activity/Tools/MCP/Subagents); stacked daily-by-model bars + "By project"
+ list; the **model→project Sankey** signature. Sankey needs the new flow emitter.
+3. **Optimize** — segment tabs (Waste / Reverts / Abandoned / Fixes) with dollar totals; ranked findings with
+ evidence (mono) + copy-fix chips. Waste from `optimize`; reverts/abandoned from new `yield --json`.
+4. **Models** — pricing table with per-model series dots (calls/input/output/cache/cost/saved), unpriced-row
+ "add alias" affordance, Compare mode. Source `models --json`.
+5. **Plans** — vendor-cycle plan tracks (gradient fill; red past 100%), pace warnings as text. Source new
+ `plan --json`.
+6. **Settings/Devices** — settings rail (General/Providers/Aliases/Plans/Devices/Export/Privacy); Devices pane:
+ this device, discovered-nearby (approve), paired list, "combine usage" toggle. Source devices/share JSON.
+7. **States & trust** (cross-cutting) — per-provider empty/permission-denied/loading/provenance states, wired
+ from CLI exit codes + partial payloads.
+
+Window chrome (traffic lights, app mark, sidebar nav with ⌘1–⌘5 / ⌘,, period segmented control, provider
+popover, ⌘K affordance, footer hints) is shared across all sections.
+
+## 7. Task decomposition (for delegated implementation)
+
+Fable orchestrates and reviews; implementers are **Opus 4.8 (primary)** and **Codex 5.6-high (alternate)**.
+
+- **T0 — Scaffold** (`app/` Electron+Vite+React, main/preload/renderer, IPC bridge, `indigo.css` port,
+ shared `components/`, window chrome + sidebar nav, `codeburn` path resolution + one working IPC call).
+ → Opus 4.8. **Blocks all section work.**
+- **T1 — CLI JSON emitters** in `src/` (TypeScript): `spend … flow-json` (Sankey matrix), `yield --json`,
+ `plan --json`, confirm/add `devices/share … --json`; unit tests in `tests/`. → Codex 5.6-high. Parallel with T0.
+- **T2 Overview**, **T3 Spend**, **T4 Optimize**, **T5 Models**, **T6 Plans**, **T7 Settings/Devices** — one
+ self-contained component + typed hook each. Split across Opus 4.8 + Codex 5.6-high. Each depends on T0;
+ T3/T4/T6/T7 also depend on their T1 emitter (mock the typed payload until the emitter lands, then swap).
+- **T8 — Integration pass**: 30s polling, refresh-on-action, error/empty/loading states end-to-end,
+ README + `npm run app:dev`. → Opus 4.8.
+
+Every returned unit comes back to Fable for review before it counts as done.
+
+## 8. Milestones
+
+- **M1 (this effort):** app runs via `electron .` (dev), all six sections rendering real CLI data (⚠ gaps
+ filled by T1 emitters), dark theme, honest states. No packaging.
+- **M2 (follow-up):** electron-builder packaging, code-sign + notarize, `codeburn app` launcher subcommand
+ (mirroring `codeburn menubar`), auto-update, light theme.
+
+## 9. Testing / verification
+
+- **CLI emitters (T1):** vitest in `tests/` against fixtures (the repo's established pattern).
+- **Renderer:** component render tests for each section against typed mock payloads (Vitest + RTL; add to
+ `app/`). Typecheck via `tsc --noEmit`.
+- **End-to-end smoke:** launch `electron .` against real local session data; verify each section populates,
+ period/provider switching re-polls, and CLI-missing → first-run state. Verified by Fable before sign-off.
+
+## 10. Risks / open items
+
+- **Data-shape drift:** hand-kept types between `src/` and `app/` can diverge (same risk `dash/` carries).
+ Mitigation: copy menubar types verbatim; T1 emitters ship with typed fixtures the renderer reuses.
+- **Sankey matrix cost:** model×project aggregation may be non-trivial in `usage-aggregator.ts`; if it slips,
+ Spend ships bars+projects first and the Sankey lands in a fast follow.
+- **Provider-name / project-path privacy:** local app shows real project names (unlike shared/sanitized data);
+ fine for a local-only window, but keep the sanitize boundary in mind for any future device-pull views.
+- **Electron version / security defaults:** pin Electron, enable `contextIsolation`, disable `nodeIntegration`,
+ restrict `preload` surface; no remote content loaded.
+```