codeburn/docs/design/codeburn-desktop.md
iamtoruk 82f716d335 docs(desktop): design spec + wireframes for standalone CodeBurn Desktop app
Standalone Electron app rendering the v6 "indigo instrument" wireframes,
fed by the codeburn CLI via the menubar JSON contract (spawn codeburn --json,
decode, poll). Six sections: Overview, Spend, Optimize, Models, Plans,
Settings/Devices. Data aggregation stays CLI-side; renderer never fakes data.
2026-07-10 14:28:16 -07:00

182 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <subcommand> --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 (`<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.
```