* feat(windows): configure the menu bar and Capacity Dock as a Plugins card
Windows now configures its tray companion from a card on the Plugins page, the
same place macOS does, instead of two switches in the sidebar corner.
- WindowsCompanionCard mirrors the macOS MenuBarCard: Install/Reinstall,
Update, Open (show the tray popover), Settings (opens the desktop Menu bar
settings pane), Quit (stop without uninstalling), Uninstall, plus the
Capacity Dock toggle, a Running dot, the version, and an info popup.
- MenubarCompanion.status() now reports installed/running/version/outdated/
canInstall, and new discrete install/open/quit/uninstall actions back the
card. uninstall only forgets the tray once the CLI removal succeeds, and a
cancelled elevation prompt is treated as a no-op rather than a failure.
- isNewerVersion sorts a dev build below any release, so a staged release still
lights Update and a dev build never does.
- codeburn menubar --uninstall reads the product code from the install marker
or the uninstall registry and runs msiexec /x.
- Remove the bespoke sidebar CompanionSwitches and their dead CSS.
- A dev-only CODEBURN_MENUBAR_RESOURCES override lets the tray install run
without packaging; the CLI still validates the staged path in full.
* feat(desktop): fold the Capacity Dock settings into the Menu bar pane
One tray rail entry instead of two: MenuBarPane and CapacityDockPane render
together in the Menu bar pane with a clear gap between the two cards, so the
card's Settings button opens both. Drops the now-unreachable 'dock' pane value.
* fix(desktop): stop the Spend cost-flow Sankey from re-rendering forever
The label-fit effect measured the already-truncated text, so a capped label
read as short, un-capped, overflowed the cap, and re-capped without end
(Maximum update depth exceeded). It now estimates each label from its full
name via data-name, and caps only tighten per layout, so the measure, cap and
remeasure cycle is guaranteed to settle instead of oscillating.
* feat(desktop): bundle Inter and JetBrains Mono for one UI on every OS
The app used each platform's system font (SF Pro on macOS, Segoe UI on
Windows), so the two looked different. Bundle Inter for text and JetBrains
Mono for figures and lead the --sans and --mono stacks with them, keeping the
per-locale CJK fallbacks, so Windows and macOS render the same faces.
* desktop: gate tray installed state on support; drop strings and channels the sidebar switches left behind
Fresh installs now default to the light theme; an explicit theme choice is still honored. Type and spacing move one step up (larger body, titles, sidebar rows and page padding), sidebar shortcut chips move to hover tooltips, and primary text gets full contrast in both themes.
The Plugins page shows the Menu bar and Teams plugins as two cards side by side with embossed background art. The Menu bar card carries the Capacity Dock switch and Settings, Quit and Uninstall as icon buttons with tooltips, an info modal describing the menu bar and the Capacity Dock with the real supported-provider list, and it can install, update, open, quit and uninstall the menu bar app, versioned so an older one shows Update, with a Settings action that opens the menu bar app's own settings. The Teams card shows what is planned in an info modal and a Register for beta testing link.
The Plans page is redesigned as provider cards in a responsive grid, each with its own logo embossed. Connection status is now stable: a single empty or slow poll no longer flips a connected provider to disconnected, a connected provider keeps its quota bars through a transient refresh, and a stuck waiting resolves to an actionable state after twenty seconds. A login-expired error offers Connect, and Gemini's expired login is now a terminal actionable state instead of an endless wait, matching the menu bar.
CLI report, models and menubar-json numbers are identical to the cent before and after; only the Gemini quota error string changes.
Measured design audit fixes, one type ladder and header, zero clipping at 900 to 1680, light-theme contrast, Lucide icons, shared Escape, plurals and provider names, motion tokens with zero refresh shift, black frame with rounded content card, grouped monochrome sidebar with collapse, two-layer card system on every screen, Overview gauge, sparkline cards and chart grid, wallpaper onboarding, Windows fixes. Every number diffed identical to main on a fixed range; 1,032 app tests passing.
New sidebar section 'periods' (Compare periods, shortcut 9) rendering
sections/PeriodCompare.tsx: preset last-7-complete-days vs prior 7 or two
custom calendars with a swap control; range meta shows both spans, the
duration delta and any overlap; totals, normalized per-day and per-100-calls
tables (denominators printed, zero denominators render as em dash); project
and model contribution lenses with New/Gone badges and click-to-inspect
session drill-down joined across A and B; coverage block for unpriced models
and aggregate-only carried history. A/B selection, lens and view persist in
localStorage so returning to the screen keeps the selection.
Navigation adapter until the goal-8 navigation lands: onInspectRange opens the
existing Sessions section scoped to the requested range via the app's
custom-range state; the Overview entry card links here. Renderer styling
follows the existing panel/table conventions.
Includes component tests (fixture report, New/Gone labels, per-100-calls
recomputation with zero-call sides, persistence across remounts, lens
switching) and user documentation at docs/compare-periods.md.
The tray switches took the sidebar corner on Windows and the social
links moved to About, but the switches render nothing off Windows, so
Mac and Linux users lost the row and gained nothing. The links are back
there on every platform but Windows.
The rail is a window of the tray app, and every setting it renders from belongs
to the tray app, so a Sidebar that is on while the menu bar app is off describes
something that cannot exist.
Turning Menu bar off now turns Sidebar off with it, and writes enabled: false
into windows-dock.json before sending --quit, so what the tray app finds the next
time it starts is the state the switches are showing rather than a rail that
comes back on its own. Turning Sidebar on with Menu bar off turns Menu bar on
first, installing and starting the tray app and writing the Run value, and only
then enables the rail; if the tray app cannot be turned on there is nothing for
a rail to live in, and both switches stay off.
In the corner the Sidebar switch is disabled while Menu bar is off, with a title
saying why. That is the primary guard, and the cascade above is what answers a
request that arrives any other way: through the IPC channel, or from a settings
file that already disagrees with itself.
The sidebar's bottom corner held About and a row of five brand glyphs. The
glyphs were the least useful thing on that screen: they are visited once, and
About already listed every one of them under Links. So they are only there now,
and the corner carries the two things a Windows user does want at hand: whether
CodeBurn sits in the notification area, and whether the Capacity Dock rail is on
the screen edge.
Both are on by default and neither renders at all until the main process says
this build has a tray app staged, which is why there is no placeholder row and
no greyed-out switch: on macOS, on Linux, and in a dev build with nothing
staged, the corner is exactly what it always was.
The switch is the shared one from Settings and onboarding, at 30x17 rather than
38x22 because a 186px sidebar has no room for the full size beside a label, and
keyed off the theme tokens rather than the white overlays the bare shape uses on
the dark onboarding sheet.
Part 1: Add --json output flags to three CLI commands for machine-readable output
- codeburn plugin list --json: array of plugin objects with status and capabilities
- codeburn plugin info <name> --json: manifest with dir and onDiskSections
- codeburn sync auto status --json: status object with configured, accepted, killed, receipts
Part 2: Implement Electron bridge with read and mutation handlers
- Read handlers: pluginList, pluginInfo, syncAutoStatus (return parsed JSON)
- Mutation handlers: pluginAdd, pluginRemove, pluginVerify, syncAutoEnable, syncAutoDisable
- All handlers follow existing Envelope pattern with proper error handling
- Special case: syncAutoEnable captures disclosure text when accept=false
Part 3: Renderer UI components (minimal implementation D1-D3 shell)
- Add Plugins section to nav rail at bottom (quiet entry, plugin icon)
- Implement PluginsSection component with list rendering for loaded/rejected plugins
- Update Section type and routing to include plugins navigation
- Add test coverage for Plugins panel row states
- Implemented as dormant Team tab registry placeholder for future plugin sections
Tests: All CLI tests (3839 passed), app tests (613 passed), E2E gates all passing
- CLI: tsc clean, vitest passes with 26 pre-existing jsdom errors
- App: typecheck clean, npm test green, npm run build succeeds
Closes#918.
The renderer's keydown handler required event.metaKey and explicitly
rejected event.ctrlKey, so every shortcut was dead outside macOS: on
Windows and Linux metaKey is the Super key, which the OS shell takes.
Navigation (1-8), Settings (,) and Refresh (R) all did nothing. The
sidebar and footer hints also hardcoded the Cmd glyph, so a Windows
user was shown chords that could not fire.
Add app/renderer/lib/platform.ts as the single source of truth for
platform-aware shortcuts, reading the platform the preload already
exposes (window.codeburn.platform) with a user-agent fallback for the
non-Electron cases. isModifierChord accepts Cmd-without-Ctrl on darwin
and Ctrl-without-Cmd elsewhere; altKey stays rejected on both, because
AltGr on European Windows layouts arrives as Ctrl+Alt and must not
hijack a typed character. Every visible shortcut label now resolves
through shortcutLabel() at render time, so the sidebar shows Ctrl+1
where macOS shows the Cmd glyph.
The mac chord condition is unchanged: the old guard admitted
metaKey && !altKey && !ctrlKey && !shiftKey, and the new one admits
exactly the same set on darwin.
The Electron application menu is deliberately left alone. It ships no
reload/forceReload role and no CmdOrCtrl+R accelerator, which is what
leaves Ctrl+R free for the renderer to handle on Windows.
Also corrects the Settings navigation hint, which read 1-7 while the
sidebar has eight numbered destinations.
Tests cover both platforms for labels and dispatch, including the
negatives: Meta on win32, Ctrl on darwin, and the Ctrl+Alt AltGr shape.
Part 1 (src): expose two add-only, optional aggregations on the menubar
payload's `current`, computed on the unscoped all-provider path from the
surviving-session parse (carried history cannot contribute, as expected).
- current.pullRequests: aggregateByPr rows (top 20 by cost) plus the
multi-link-safe distinctCost/distinctSessions. Rows are by-reference, so
they are never summed.
- current.byBranch: new aggregateByBranch, per-branch spend (top 15 by cost)
that carries each session's last-seen git branch forward across its turns.
The cache stores a turn's branch only when it changes, so the parser now
resolves the branch at reconstruction (before the date slice) and records
SessionSummary.everHadBranch from the full transcript. That lets the report
keep a branch-bearing session's pre-branch spend in an explicit null row
even when the range clipped the anchor turn, while a provider that never
captures a branch still contributes nothing.
Part 2 (app): new "Pull requests" sidebar entry between Sessions and Spend
(shortcuts reflow to number 8 for Plans). It renders the pullRequests rows as
a refined table with the PR label linking out via openExternal, cost,
sessions, calls and an active-date span, plus a footnote stating the distinct
total and the by-reference attribution. A quiet explanatory line shows when no
PR links exist, never a fake table.
Tests: aggregateByBranch unit tests (carry-forward, null bucket, clipped
anchor, by-reference session counts) and the Pull requests component tests
(table, external link, footnote, both empty states). Nav-reflow assertions in
Sidebar and App tests updated.
Co-authored-by: reviewer <review@local>
The owner supplied the final flame render; the hand-retrace is retired.
Checkerboard keyed out to real alpha, composited to the Big Sur squircle
(824/1024, r185, charcoal #231613) as app/build/icon.png (the
electron-builder master, verified legible at 16px), and the flame-only
alpha PNG becomes the in-app mark: FlameMark now renders the raster so
splash, sidebar, About and dock are pixel-identical. Flicker applies to
the whole mark (raster has no separable core). Stale icon.svg removed.
Flame suites green in isolation; full suite re-verified before the
in-flight quota batch commits.
- FlameMark: hand-traced inline SVG of the brand flame (~1.1KB, warm
3-stop gradients, useId-scoped), used at splash and sidebar sizes
- Splash: full-window first-load overlay echoing the menubar's
BurnLoadingOverlay ignite moment; 600ms floor so warm cache never
blinks, 250ms crossfade on first data, instant yield on error or
reduced motion, done-latch so it never reappears
- Sidebar logo replaced with the live FlameMark: 1-2% inner-tongue
flicker, 4.5s period, random phase; static under reduced motion
250/250, build green (+2.4KB JS, +1.8KB CSS).
Sessions: provider-grouped list of every session in range (project,
models, turns, cost, tokens) with a lightweight real detail — no
fabricated titles, no unbacked context-window widget.
Compare: two-model picker (defaults to top two by cost) across
Performance, Efficiency, Category head-to-head, Working style, and a
derived Context panel, all mapped straight from `compare --format json`.
Nav reordered to match the design: Overview, Sessions, Spend, Optimize,
Models, Compare, Plans, Settings (⌘1-7).
Ports the wireframe window shell into React with exact markup/classes:
- Shared components: Window, Sidebar (traffic lights, gradient mark, six
nav items with ⌘ keycaps + active `on` rail, status line), TopBar
(title, scope, period SegTabs Today/7D/30D/Month/6M/Custom, ProviderPop),
Panel, Stat, SegTabs, ProviderPop, Hint.
- usePolled(fetcher, deps): generic 30s poll + loading/error state,
errors normalized to CliError.
- App.tsx: local section state switches the content outlet; the Overview
placeholder round-trips getOverview('30days','all') and renders the raw
current.cost, with an honest first-run "locate CLI" state when the
codeburn binary isn't found (never a crash).
- Sidebar.test.tsx: six nav items, click routes onNavigate, active `on`.