Commit graph

64 commits

Author SHA1 Message Date
Resham Joshi
2eb4718122
sessions: correlate cross-provider work with pull requests (#792) 2026-07-21 11:50:11 +02:00
Resham Joshi
45e93129f2
sessions: attribute PR spend per turn instead of per session (#790)
* sessions: attribute PR spend per turn instead of per session

The by-PR surfaces attributed a session's full cost to every PR it
referenced, so one orchestration session that touched many PRs rendered
identical full-session rows. Capture per-turn PR references during
transcript parsing and attribute each turn's cost to the PR set active at
that turn (split evenly across a multi-PR merge-sweep turn), carrying the
most recent PR set forward across turns that reference nothing. Turns
before the first reference form an unattributed bucket.

A session whose transcript expired before per-turn capture keeps its
session-level prLinks but has no per-turn refs; it falls back to an even
whole-session split, and any row carrying such a portion is flagged
approximate.

Rows are now summable: the CLI and app footers report the attributed sum
plus the unattributed remainder instead of a distinct-session total.
Bumps the session cache version so surviving transcripts re-parse and
populate the new per-turn field. Daily-cache versioning is untouched.

* sessions: harden PR attribution and add models + category breakdown

Addresses the review findings on the per-turn PR attribution and adds the
model and task-category surfaces.

Correctness:
- Cache migration: the 5 -> 6 session-cache bump now adopts the prior v5
  file's expired-source PR entries instead of abandoning them, and the claude
  scan preserves and surfaces PR-bearing orphans, so a session whose transcript
  was deleted still appears as a legacy even-split instead of vanishing. The
  daily cache is untouched.
- Date-range carry: the parser captures the PR set active at the start of the
  in-range turn slice and seeds the state machine with it, so a PR referenced
  before the window still owns its later, in-range, ref-less turns instead of
  the session falling back to a whole-session approx split.
- Calls are split across a multi-PR turn by largest-remainder, keeping per-PR
  counts whole (a 1-call, 2-PR turn no longer renders as 2 calls).
- The CLI and app footers reconcile to the rounded row values actually shown.
- Distinct sessions are keyed by project + sessionId, not sessionId alone.
- The app tolerates an older by-reference payload (no attributedCost): it keeps
  the old non-summable footer and never renders NaN.

Features:
- Each PR contribution records the models of its calls and the turn's task
  category (split by the same share on a multi-PR turn); legacy even-split rows
  carry the model union but no category breakdown.
- Payload rows gain models (short names, cost-desc) and categories (label + cost,
  cost-desc, omitted when empty); the payload gains otherPrCount/otherPrCost for
  the PRs beyond the sent top 20.
- CLI --by-pr gains a Models column. The desktop table is now full-width with a
  Models column and click-to-expand rows showing a per-category cost breakdown
  with proportional bars, keyboard accessible, with an "Other (N more PRs)" row
  when capped.

Tests: state-machine seed/models/categories/largest-remainder/dedup, the prRefs
round-trip through the real incremental-append path (continuation + straddle),
v5 adoption of an expired PR session, payload round-trip, and the desktop
expansion/models/old-payload cases.

* sessions: reconcile mixed PR rows, harden v5 adoption, bound models

Round-2 review follow-ups.

- Mixed legacy/live category breakdown: a PR row that combines an expired
  legacy contribution (even-split, no turn data) with a live per-turn
  contribution now emits an explicit "Legacy estimate (no per-turn detail)"
  category carrying the legacy share, so the expansion reconciles to the row
  cost instead of silently dropping it. A legacy-only row still shows no
  breakdown.
- v5 adoption is now per cached file: one malformed entry is skipped instead
  of rejecting the whole v5 cache and dropping every valid expired PR session.
- The desktop "Other (N more PRs)" line moved to the table footer as a muted,
  separated summary rather than a sorted row.
- Row expansion resets when the PR set changes (period/provider/data), so a
  stale expansion cannot linger on a row that is gone.
- Payload models per row are capped to the top 4 by attributed cost, with a
  name-ascending tie-break; categories get the same stable tie-break.

Tests: mixed live+legacy reconciliation, legacy-only omission, model cap and
tie-break, corrupt-plus-valid v5 adoption, and the desktop expansion-reset.

* session-cache: validate optional agentType and failed during v5 adoption; app: reset PR expansion on period switch

A v5 entry that was valid except for a malformed optional field (agentType,
failed) passed per-file validation and flowed downstream as a non-string.
The expansion reset keyed only on the row URL set, so a period switch that
returned the same PRs kept a stale expansion open over changed numbers.

---------

Co-authored-by: reviewer <review@local>
2026-07-20 20:44:29 -07:00
Resham Joshi
a9d4471d17
payload: spend by pull request and by branch; desktop Pull requests tab (#788)
Some checks are pending
CI / semgrep (push) Waiting to run
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>
2026-07-20 14:36:53 -07:00
Resham Joshi
4d2bcbac7f
app: Workflow card on Overview and session titles in the Sessions view (#784)
Two Phase-1 desktop additions over the renderer's existing card grammar.

Overview workflow card: a panel below the daily chart with two stat tiles
(correction rate with count, median time to first edit) plus a top-rework
line and one coaching caption. The caption is derived locally with the CLI's
buildCoachingNotes thresholds (corrections, then file churn, then time to
first edit; first that fires). The card hides entirely when no real signal is
present, never renders zeros, and shows a "N% priced" chip only when pricing
coverage is a number below 1. Payload types gain optional workflow,
topReworkedFiles, and pricingCoverage fields so older payloads render as before.

Sessions view: the captured session title becomes the row headline with the id
demoted to the mono secondary line; untitled rows keep the project as the
headline (unchanged). Title is also searchable and added as an optional
SessionRow field so older CLIs render unchanged.

Co-authored-by: reviewer <review@local>
2026-07-20 13:55:33 -07:00
Resham Joshi
dbd93fee51
app,dash: render pre-history days as no data, not a currency zero (#765)
* fix(app,dash): render pre-history days as no data, not a currency zero

Days before the first recorded day in history.daily were painted as a real
0.00/0 calls in the daily activity heatmap and the daily spend charts. That
zero is unknown, not measured, so those cells and bars now read "No data
recorded" instead of a currency zero. Genuinely idle days within recorded
history stay as real zeros.

- app: heatmap cells, the daily spend bars, and the daily-by-model columns
  get a distinct no-data style plus a "No data recorded" hover for days before
  the first recorded day.
- dash: the granular line chart drops buckets before the first recorded day so
  no flat zero line is drawn before any history exists.
- data-start is derived in the UI layer from history.daily; payload shape is
  unchanged.

Verified: app vitest 418 pass (7 new for the no-data behavior); app, dash, and
root typecheck clean; dash and app renderer vite builds succeed.

* no-data: cap guard, timezone-free dash trim, StackedBars aria

Three hardening fixes from adversarial review of the no-data rendering:

dataStartKey returns null at the payload's 365-entry history cap, where
the oldest retained entry is no longer the true data start; classifying
past it would label real aged-out history as no data on long custom
ranges. Documented that the install-to-first-use gap reading as no data
is literally accurate: nothing was recorded then either.

The dash granular chart now trims leading zero-only buckets by value
instead of comparing bucket timestamps against date keys, so producer
and viewer timezone skew can never drop a real first-day bucket, and an
all-zero series lands in the established empty state.

StackedBars no-data columns expose their state via aria-label, not only
a title on a non-focusable div.
2026-07-20 08:03:24 -07:00
iamtoruk
0a76626d21 fix(app): clean build stamp, splash without hash, About checks for updates
- build stamp ignores the pricing-snapshot files the build regenerates,
  so a release checkout no longer shows a false -dirty
- splash shows just the flame, CodeBurn, and the version (dropped the
  commit-hash line); the clean build id stays in About only
- About 'Check for updates' uses the in-app checker and reports inline
  ('You're on the latest' / 'Update available: X · Download') instead
  of blindly opening the releases page

392/392.
2026-07-17 14:30:00 -07:00
iamtoruk
8fc9e303b9 feat(app): update-available notifications
Checks the GitHub releases for newer desktop-v tags once per launch
and every 24h (15s timeout, no identifying headers, offline = silent
retry). A dismissible banner and a Settings About row surface the
newer version and link the release page; dismissal persists per
version so each release nags at most once. No auto-install: unsigned
builds cannot, by platform constraint; the checker only notifies.

App 364/364 (+24), typechecks + build green.
2026-07-17 04:58:13 -07:00
iamtoruk
e4bbcaaba7 fix(app): zero background work when hidden; currency applies instantly
Energy (field report: ~3x Chrome drain):
- polls skip entirely while the window is hidden/minimized, with one
  catch-up refresh on return when data is stale; visible-but-unfocused
  keeps polling (second-monitor case)
- all looping animations pause under html.page-hidden (the sidebar
  flame flicker was a perpetual compositor drain)
- default cadence 60s; an explicit stored choice is always honored

Currency (field report: set USD, still saw EUR):
- memo-served payloads re-applied their embedded stale currency; config
  mutations now purge the renderer memo and force-refresh, and a
  switching payload can never overwrite the applied currency
- USD default verified end to end

Measured: hidden window = 0 new CLI spawns over 5 cadences (was: full
polling forever). App 340/340, build + package green.
2026-07-16 18:41:28 -07:00
iamtoruk
ae18b1fd91 fix: audit batch — prefetch one-shot, turn-anchored day bucketing, warm-path collapse
- prefetch fires exactly once per provider per session; memo sized to
  detected providers so base keys never evict (was: 12 full-history
  parses every 30s forever from LRU thrash re-triggering the effect)
- day-aggregator buckets whole turns by user-message timestamp,
  matching the report/headline path: trend bars and provider breakdown
  now reconcile exactly (midnight-straddling turns were split);
  DAILY_CACHE_VERSION 13 with an IANA tz guard that rehydrates when
  the cache crosses timezones
- overview's redundant second all-provider scan collapsed (~11% faster
  warm); numbers parity-guarded
- lock takeover cleans stale hydrating.lock; SIGINT/SIGTERM release it
- scanner skips EPERM/EACCES provider dirs (progress shows skipped)
  instead of aborting; onboarding hints Full Disk Access
- rateLimited quota copy render tests; export -f json emits valid
  empty JSON when no data

Root 2023 (+4), app 327 (+3), typechecks clean.
2026-07-16 15:37:03 -07:00
iamtoruk
e9655fa03c fix: hydration completeness markers, cold-only splash, honest switching, prefetch
P0: an interrupted cold hydration left partial caches that the
emptiness check read as warm — the daily backfill froze (missing older
days) while session parsing healed gradually (drifting totals),
poisoning every surface sharing the cache. Both caches now carry an
explicit complete marker: partial resumes cold under the hydration
lock; unmarked caches self-heal with one re-hydration. Regression test
seeds the exact frozen artifact and asserts bit-for-bit convergence
with a never-interrupted run.

- splash indexing reveals only on genuinely cold scans (explicit cold
  flag in the progress protocol), never on warm incremental re-parses
- uncached filter switches clear to skeleton instead of showing the
  previous filter's numbers; cached switches paint same-commit;
  background prefetch warms every detected provider after idle
- build stamp (git sha + date) in splash and About ends build ambiguity
- payload parity test: identical payloads on identical cache, and
  payload totals equal the CLI report path
- Sessions empty state keeps the provider filter row; zero savings
  line hidden; TCC usage-description strings + Full Disk Access docs
- warm profile documented: optimize scan ~1.47s dominant (safe
  refactor deferred), parse ~1s, aggregation ~30ms

Root 1848, app 320, typechecks clean.
2026-07-16 14:05:11 -07:00
iamtoruk
300e949951 feat(app): refresh cadence, instant switching, cache coherence, telemetry v1
Performance and coherence:
- Settings > General 'Refresh every' (Manual/30s/1m/3m/5m/10m), live via
  RefreshCadenceContext; Manual polls only on demand
- usePolled memoKey LRU: provider/period switches paint the last-good
  result instantly with a switching hairline while refreshing quietly
- quota TTL 5min + honest rate-limited copy on 429 backoff
- version-suffixed cache files (session-cache.v5.json, daily-cache.v12,
  auto-minted on future bumps); legacy files never written or deleted,
  adopted once when versions match: old and new binaries coexist
  without clobbering (field-observed menubar-vs-desktop ping-pong)
- advisory hydration lock: concurrent cold starts share one scan
  (wait-then-read-warm), stale/dead locks self-heal, never a
  correctness gate

Telemetry v1 + onboarding (desktop only, per owner decisions):
- first-launch onboarding (3 feature screens + consent); region-split
  default (EU/EEA/UK/CH off, elsewhere on, unknown off); nothing sends
  before consent completion or while off; dev builds never send
- anonymous install UUID, rotated on opt-out; day-granularity events,
  cost buckets only; whitelisted names; 200-event queue, 5min flush
- Settings > Privacy live toggle replaces the static claim

Zero computed-number changes. App 316/316, root 1805.

Wire contract targets api.codeburn.app/v1/telemetry (Worker follows).
2026-07-16 11:47:05 -07:00
iamtoruk
d5b18ff3b4 polish(app): compact first-run splash
The per-provider ingest list rendered a dozen stacked text rows on
machines with many detected providers. Now one status line with live
tabular-numeral counts (Indexing Claude · 240/699), a single strip of
15px provider logos (dim pending, pulse active, settled done), and a
muted one-time-scan note. ~3 lines of vertical footprint, 340px max
width; reduced motion drops the pulse; warm launches never see it.
2026-07-16 10:59:20 -07:00
iamtoruk
55bc53e6be fix(app): cold start survives, first-run shows per-provider progress
Root cause of the field-reported 45s timeout + perpetual slowness: on
a cold cache all six sections spawned different CLI subcommands at
once, each running its own full-history parse (~11-31s, ~3GB RSS),
contending past the 45s kill so the rebuilt cache never persisted and
every poll restarted from zero.

- the first overview fetch runs as a warmup: 10-minute timeout,
  re-arms until it succeeds, reverts to 45s after; section polls gate
  on that first resolution (usePolled gains enabled), so cold
  hydration happens exactly once
- warmup streams the CLI's progress protocol; the splash shows 'First
  run: indexing your usage history' with a per-provider ingest list
  (logo, live counts, check on done), static under reduced motion,
  generic fallback without events; warm launches unchanged
- overview spawns pass --no-timeline (desktop never renders it)

Measured on real data: cold 31s once (was: killed at 45s forever),
warm 2.6s. 295/295 app, 1797 root.
2026-07-16 10:51:18 -07:00
iamtoruk
28d294cde5 fix(app): provider logos key on internal ids + monogram fallback
Settings Providers pane keyed logos by lowercased display names
('grok build' never matched THEMED_LOGOS 'grok'); rows now come from
providerDetails ids like the rest of the app. 20 official marks added
from the repo's own assets (hermes, openclaw, antigravity, kiro, devin,
cline, roo-code, ...) and unknown providers render a first-letter
monogram badge so no detected provider is ever logoless.
2026-07-16 09:27:46 -07:00
iamtoruk
9517d63ba3 fix(app): loader video floats with real alpha (no background)
The h264 loader baked a flat dark background that read as a rectangle
against the splash's warm radial tint. Re-encoded from the original as
VP9 WebM with an alpha channel (checker keyed to transparency), so the
burning flame composites directly onto the gradient. Verified over a
contrasting test canvas.

273/273, typecheck + build green.
2026-07-16 06:22:32 -07:00
iamtoruk
000e39a034 feat(app): binary-flame brand art + animated video loader on the splash
New owner-approved art: the binary-digit flame. Checkerboard keyed out,
largest-component masking to drop glow-zone checker fringe, clean
synthetic ember glow rebuilt; composited to the Big Sur squircle as
app/build/icon.png and squared with alpha as the in-app FlameMark
raster (sidebar, About, static splash).

The supplied burn animation becomes the splash loader: checker keyed
per-frame and the splash's fixed dark canvas (#171310) baked in (h264
has no alpha), audio stripped, 944KB looping video shown while the
first scan runs. Splash canvas and text colors are now theme-fixed so
video and page never diverge; reduced motion/tests keep the static
mark, verified by test.

Flame/splash/motion suites green in isolation (full suite re-verified
when the in-flight quota batch commits); typecheck + build green.
2026-07-16 06:19:12 -07:00
iamtoruk
3a0f4b2fb9 feat(app): retraced brand flame — one mark for icon, splash, sidebar, About
- app/build/icon.svg + icon.png: hand-retraced vector of the approved
  reference (three flat layers: coral #e86c39, amber #f7943c, cream
  #fecb8b on charcoal #231613), Big Sur squircle proportions (824/1024,
  r185, transparent corners, no glow), flame ~72% optically centered;
  8 render-and-compare iterations against the reference; legible at 16px
- FlameMark retraced to the same three paths (flat fills, no gradients);
  flicker now animates the cream core only
- AboutModal renders FlameMark instead of the old gradient flame.png;
  orphaned 70KB asset removed

253/253, typecheck + build green.
2026-07-16 05:54:40 -07:00
iamtoruk
1320807de2 polish(app): splash wordmark in brand orange + version line
CodeBurn wordmark on the splash now uses --accent (matching the
website/sidebar brand treatment) with the app version (v0.1.0, from
package.json at build time) in muted small text below. App version
bumped 0.0.0 -> 0.1.0.

253/253, build green.
2026-07-16 05:15:35 -07:00
iamtoruk
0f836db72f fix(app): flame mark is the real menubar flame
Replaces the rejected hand-traced silhouette with Bootstrap Icons'
fire path (MIT, embedded with license note) — the flame.fill-style
teardrop with the inner tongue — filled with the menubar BurnFlame's
actual Ember gradient from the Swift theme (#f0a070 > #e8774a >
#c9521d > #8b3e13, bottom to top), not the brighter accent ramp.
Flicker hook moves to the whole mark (single compound path).
Also: .splash-word snapped to var(--fs-kpi).
2026-07-16 05:11:10 -07:00
iamtoruk
77f791376a feat(app): flame splash startup loader + living sidebar flame
- 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).
2026-07-16 04:51:30 -07:00
iamtoruk
daf944e714 feat(app): motion layer (gsap) — count-ups, chart grow-in, section fade, skeletons, toasts
- one gate (motionEnabled): off under reduced-motion, missing matchMedia,
  and vitest; every path checks it first
- mount/filter-change only: count-up and bar grow-in key off the
  period|provider|range key, so 30s poll refreshes snap values silently
  instead of re-animating
- first-load skeleton shimmer replaces bare scanning text (kept sr-only
  for screen readers); slide-in toast host for Settings/export feedback
  (validation errors stay inline); CSS hover-lift + press micro-
  interactions, all with a reduced-motion escape hatch
- gsap 3.15.0 + @gsap/react 2.1.2 (+74KB raw JS; G4 flame work shares it)

244/244, typecheck + build green.
2026-07-16 04:39:09 -07:00
iamtoruk
26ffd60e0f polish(app): retire the legacy token family
Every var(--t1/--t2/--t3/--hair/--hair2) and secondary legacy color
(--lav/--amber/--red/--mint/--blue/--purple/--cyan) repointed to the
canonical family (--ink/--mut/--mut2/--line/--line2, --warn/--bad/--ok,
--s-*); all legacy definitions and the plain.css alias shim deleted.

The explicit dark/light theme blocks carried drifted legacy literals
(including a pre-contrast-fix --t3), so this also extends the AA
contrast values to all four theme modes, which previously disagreed
with system-light.

grep-proven zero legacy usages. 235/235, build green.
2026-07-16 04:16:44 -07:00
iamtoruk
0eb0fd60d7 polish(app): spacing scale, unified insets, shared banner classes, scrollbar cues
- --sp-1..7 scale; card headers unified on 10x14, bodies on 12x14;
  orphan gaps snapped to rungs
- CapsuleChart's orphaned CSS deleted
- StaleBanner/budget/empty/range notes moved from inline styles to
  plain.css classes; Panel bodyStyle prop retired (scroll-x class)
- thin always-visible scrollbars on scrolling panels and overflow-x
  tables; Compare pickers flex below 760px instead of overflowing
- one capitalization fix (loading quota…)

235/235, build green.
2026-07-16 04:02:21 -07:00
iamtoruk
edaa284c0f feat(app): Audit lens on Models + Pricing pane in Settings
- Models gains an Audit lens: raw provider token fields vs displayed
  totals and cost derivation per model, est badge where pricing is
  missing or recomputed cost diverges from attributed
- Settings Pricing pane: list/add/remove price overrides (USD per 1M),
  inline confirm on remove, only provided rates sent; rates validated
  finite and strictly positive on both sides of the bridge
- four new IPC channels with the established validation pattern

Root 1617/1617, app 235/235, build green.
2026-07-16 03:45:36 -07:00
iamtoruk
09ac453fcc feat(app): coaching signals card (menubar rule parity)
Replaces the three-rule anomaly card with a Wins/Improvements/Risks
Signals card mirroring the menubar's rule set and thresholds
(FindingsSection.swift): cache >=80% and one-shot >=0.75 and spend-down
>10% and streak >=5d as wins; top-3 optimize findings, low cache <50%,
low one-shot <0.5, retry tax >=25% of spend as improvements; weekday
spike >1.8x, week-over-week >+25%, projected month >1.3x prior month as
risks. Time-comparison rules suppressed under a custom range. Renders
nothing when empty; icons per group, never color-alone. Anomaly test
coverage ported, not deleted.

221/221, build green.
2026-07-16 03:30:51 -07:00
iamtoruk
55fdbfd761 feat(app): Claude config switcher (multi-account overview scope)
- TopBar picker (shown only when >1 Claude config dir exists) threads
  --claude-config-source through getOverview; synthetic 'All Claude
  configs' entry returns to the aggregate default
- config id validation accepts the real <kind>:<16hex> id shape while
  still rejecting flag-shaped input (leading char anchored)
- both incompatibility directions handled: selecting a config resets a
  non-Claude provider filter, and picking a non-Claude provider clears
  the config scope, so the CLI rejection is unreachable
- choice persisted (localStorage); active config shown in the scope
  line and read-only in Settings/General

Verified live against 2 real config dirs. 205/205, build green.
2026-07-16 03:06:05 -07:00
iamtoruk
f8b2894b4e feat(app): Sessions provider quick-filter + Spend project inline expand
- Sessions: All + per-provider logo buttons (detected, cost > 0) that
  drive the same app-level provider state as the top-bar dropdown, so
  the two controls can never disagree; aria-pressed group semantics
- Spend By project: rows expand inline (single-open, aria-expanded,
  chevron rotation) to the project's sessions: date, top model, calls,
  currency-aware cost; date-only strings parsed at local noon so days
  never roll across time zones

194/194, typecheck + build green.
2026-07-16 02:49:17 -07:00
iamtoruk
015a1a069a polish(app): shared empty states, type ladder, AA contrast, honest affordances, a11y semantics
D1 one shared EmptyNote (7 copies removed); CliErrorPanel on canonical
   tokens; dead code deleted (CapsuleChart, stale SERIES_HEX palette,
   Stat tone API, unused rail/doc-chrome CSS)
D2 type ladder tokens (--fs-*/--fw-*); panel titles unified 13/600/ink;
   high-visibility sizes/weights snapped to rungs
D3 contrast: --mut2 5.00:1 light / 5.74:1 dark (was 2.37/3.11); new
   --accent-text 5.07:1 + persistent underline on text links; last
   prose em-dash removed
D4 'See all' and 'Most expensive sessions' navigate to Sessions; 'add
   alias' jumps to Settings/Aliases; chevrons only on clickable rows;
   inline confirm replaces window.confirm; default-period setting
   honored at boot; Sankey caption no longer promises a click; Optimize
   'improving' trend badges; cursor + Space-key fixes
D5 aria-current on nav, SegTabs as real tablist, aria-pressed toggles,
   ProviderPop rebuilt on Dropdown (one tab stop, arrow keys, focus ring)

191/191, typecheck + build green. Contrast ratios independently
recomputed. Net -93 lines.
2026-07-16 02:27:41 -07:00
iamtoruk
a216b841ae feat(app): rebuild Plans on live quota (real 5h/weekly windows + auto-detected tier)
Replace the $-budget model as the primary view: per-provider cards (Claude/Codex)
from getQuota show the real 5-hour/weekly windows (% used, resets-in, severity
colors), the auto-detected subscription tier, connection states, and the credit
footer. Manual budget plans demoted to a secondary section.

typecheck clean; 148/148 tests pass. Needs a full relaunch for live quota.
2026-07-12 16:48:58 -07:00
iamtoruk
905d94253a feat(app): actionable Optimize — wire the CLI's rich findings + copy-paste fixes
- new getOptimizeReport bridge (main + preload + renderer types) running
  `codeburn optimize --format json` (needs a full relaunch)
- restore the Waste / Reverts / Abandoned / Fixes tabs
- Waste findings expand inline to show the explanation (why you're losing
  tokens) + a copy-paste fix block with a Copy button, like the TUI
- summary header: findings count / potential savings / health score

typecheck clean; 133/133 tests pass.
2026-07-12 16:28:47 -07:00
iamtoruk
82f0402abd feat(app): Spend chart — fixed 15-day window + date axis, no gridlines
- extract contiguousDailyWindow + formatChartDate to lib/period (shared)
- Spend "Daily spend by model": fixed contiguous 15-day window (zero-filled)
  with an .ov-xax date axis, matching the Overview chart
- remove the horizontal gridlines from both charts (they read like data lines)

typecheck clean; 129/129 tests pass.
2026-07-12 16:21:41 -07:00
iamtoruk
185f25f8d1 feat(app): fixed-height + scrollbar on Spend list panels
By-project and the Activity/Tools/MCP/Subagents breakdown panels cap at 300px
with a slim internal scrollbar, so they stay a consistent compact size instead
of growing unbounded with the data.

typecheck clean; 126/126 tests pass.
2026-07-12 15:56:07 -07:00
iamtoruk
bd32624afb feat(app): flatten Spend to one page (no tabs)
Drop the Projects/Activity/Tools/MCP/Subagents lens tabs. Everything shows on
one scrolling page: chart + By-project, Cost-flow Sankey, then the breakdown
sections in a responsive grid. Empty sections are hidden. All reflects the
selected date range.

typecheck clean; 128/128 tests pass.
2026-07-12 15:30:00 -07:00
iamtoruk
642a12b7ff feat(app): remove the Nearest-limit quota ring from Overview
Drop the FuelRing (and its now-unused plans polling) from the ov-stats3 card;
the stats row is now Month-to-date + Projected month (2 equal columns).

typecheck clean; 128/128 tests pass.
2026-07-12 15:25:00 -07:00
iamtoruk
49a2811093 feat(app): Settings — custom dropdowns, more currencies, fix pane layout
- new Dropdown component (our .pop menu style, keyboard-accessible) replacing
  every native <select> (currency, period, plan preset, export, Compare pickers)
- expand currencies to 30 common ISO codes (engine handles any valid currency)
- fix the Settings pane so rail + content anchor top-left and fill the area

typecheck clean; 128/128 tests pass.
2026-07-12 14:02:52 -07:00
iamtoruk
76a5db4f55 feat(app): Optimize organization + gear icon, more logos, drop relative-cost column
- Optimize: colored impact badges (High/Medium/Low) + fixed-column layout;
  fix the dead "Fixes" tab; findings no longer look falsely clickable
- Optimize nav icon: gear + magnifying glass (was a lightning bolt)
- remove the "Relative cost" bar column from the Overview models table
- provider logos: Goose / Pi / Warp + Cursor Agent (own logo) from repo assets

typecheck clean; 128/128 tests pass.
2026-07-12 13:56:07 -07:00
iamtoruk
a02ae15b6b feat(app): rebuild Sessions — fixed columns, clean paths, inline expand
- share a shortenProjectPath helper (Sankey + Sessions) so rows show
  "projects/eywa" instead of the dash-mangled full path
- fixed-column table layout so values align and stop shifting row-to-row
- TUI-style inline row expansion: click a row to unfold its 8-stat detail in
  place (one at a time, chevron + aria-expanded); replaces the navigate-away view

typecheck clean; 128/128 tests pass.
2026-07-12 13:38:45 -07:00
iamtoruk
1c236a47eb fix(app): only show used providers in picker + bottom-align hero activity
- provider dropdown: filter to providers with actual usage (value > 0) instead
  of every known/detected key (Pi, Goose, Warp, etc. no longer show)
- Overview hero: bottom-align the activity heatmap (space-between) so its grid
  lines up with the left column's Saved-to-date line

typecheck clean; 128/128 tests pass.
2026-07-12 13:26:19 -07:00
iamtoruk
6f4b2bef38 feat(app): About panel + working social links (+ LinkedIn)
- add a shell.openExternal bridge (main ipc handler + preload + renderer type)
  so links open in the default browser (requires a full relaunch)
- wire the sidebar social icons to real URLs and add LinkedIn
- AboutModal: flame + wordmark, version, tagline, the five social links, and a
  "Check for updates" link to the GitHub releases page

typecheck clean; 128/128 tests pass.
2026-07-12 13:07:38 -07:00
iamtoruk
428caab6fd feat(app): real provider logos in the picker and Settings
- add Claude/OpenAI/Cursor/Grok brand SVGs under app/renderer/assets/providers
- ProviderLogo component: brand-orange Claude in both themes; light/dark
  monochrome variants for OpenAI/Cursor/Grok swapped per active theme
- show the logo in the provider dropdown (trigger + menu items) and in
  Settings > Providers, left of each provider name

typecheck clean; 128/128 tests pass.
2026-07-12 13:00:43 -07:00
iamtoruk
3f40aeee97 feat(app): Sankey slash-paths + Models "By task" regroup
- Sankey project labels now show the last 2-3 path segments joined by "/"
  (dropping the users/home prefix) instead of the dash-mangled full path
- Models "By task" regrouped CI-style: a per-model group header (series dot +
  model + aggregate calls/cost/saved) with indented task sub-rows and aligned
  columns, replacing the flat list that repeated the model name on every row

typecheck clean; 128/128 tests pass.
2026-07-12 12:58:15 -07:00
iamtoruk
4186ad393a feat(app): color Fable as its own series (aqua)
Fable was bucketing into grey "Other" despite being a large chunk of spend.

- add `fable` SeriesKey across modelSeries.ts (label, css-var, class, matcher)
- validated CVD-safe --s-fable token (aqua #1baf7a light / #199e70 dark) in all theme blocks
- StackedBars now uses a fixed CVD-safe series order [opus, fable, haiku, gpt, sonnet, other]
  for both legend and stack, so aqua never sits adjacent to the magenta (Sonnet)

typecheck clean; 128/128 tests pass.
2026-07-12 12:52:12 -07:00
iamtoruk
b0299c57ac feat(app): design reskin — token unification, a11y, and chart color + shading
Design-consistency pass (from the audit):
- unify the indigo→plain grey/border token families (alias --t1/2/3, --hair/2 onto --ink/mut/mut2, --line/2); fix a leftover hardcoded chevron color
- accent :focus-visible rings on SegTabs, chart bars, calendar, buttons, Settings toggles; ov-link hover
- theme-aware Spend legend swatches + Sankey (fixes a light-mode contrast failure)
- wire Models "Compare…" → Compare and Plans "Add plan…" → Settings pane; drop the fake-affordance Plans label; Settings loading captions
- Compare card radius/title, set-input, alias CTA color, outer-border tokens, session mono numbers, frame spacing to match the mockup
- scope a date-flaky Overview test assertion to its card

Reskin batch 1 — color + shade (current tone):
- validated CVD-safe model-series palette via dedicated --s-* tokens (light+dark), applied to the daily bars, Sankey, and series dots
- subtle soft card elevation via --card-shadow on the main card containers

typecheck clean; 128/128 tests pass.
2026-07-12 12:36:50 -07:00
iamtoruk
1de907ac8b feat(app): Settings part 2 — Plans, Export, Devices actions, theme on boot
- Plans tab: add/remove budget presets via `plan set`/`plan reset`, lists
  configured plans, links to the full Plans screen
- Export tab: format + provider + native folder picker (electron dialog)
  → runs `codeburn export`, reports the result
- Devices: real Remove (`devices rm`) + Refresh; visibility is read-only
  from `share status`; pairing shows an honest "pair from terminal" note
  instead of a dead Approve button; combine row is read-only status
- Apply the persisted theme at app startup (not only when General mounts)
- Settings gets onNavigate for the "Open Plans" link

Only one-shot, non-interactive CLI commands are wired as buttons; the
interactive share daemon and pairing flow stay honestly read-only.
2026-07-11 17:31:32 -07:00
iamtoruk
55e9fa51f6 feat(app): functional Settings — rail nav + General/Providers/Aliases/Privacy
Settings was a dead shell (rail didn't switch, all buttons disabled).
Now the rail navigates and four tabs are wired to the real CLI:
- action bridge: spawnCliAction + runAction (text output, not JSON) with
  getAliases/getProxyPaths reads and setCurrency/resetCurrency/addAlias/
  removeAlias actions
- General: theme switcher (System/Light/Dark, persisted) + live currency
  (reads status, changes via `currency`) + default-period preference
- Providers: real detected-tools list with spend
- Model aliases: full add/remove against real config
- Privacy: honest local-only info

Devices content preserved as-is; Plans/Export are placeholders for Part 2.
2026-07-11 17:24:04 -07:00
iamtoruk
781d37674b feat(app): error boundary so a crashing screen shows the error, not a blank
A render error in any section previously white-screened the whole app
(no boundary). Wrap the section content in an ErrorBoundary keyed by
section: the error message + component stack render in a card, the
sidebar stays usable, and navigating away recovers. Logs to the console
for diagnosis.
2026-07-11 14:33:16 -07:00
iamtoruk
d9c56e3c6b feat(app): production-ready Sessions screen
Search (project/model/id), sort (cost/recent/turns/tokens), group-by-
provider toggle, and a summary bar (count · cost · tokens) over the
filtered set. Dependency-free "show more" paging keeps large lists fast;
provider headers are sticky and the list flows in the page scroll.

Richer detail: 8 real stat cards (cost/calls/turns/saved/input/output/
cache read+hit%/cache write), full date range + duration, Esc-to-back,
selection tracked by id so background refreshes keep the detail live.

Centralizes formatCompact/date/duration helpers in lib/format ("184K"
not "0.2M"); Compare now uses the shared helper. Search-aware empty state.
2026-07-11 13:50:58 -07:00
iamtoruk
e331c7da63 feat(app): Sessions and Compare screens
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).
2026-07-11 12:37:04 -07:00
iamtoruk
a39b2630c5 style(app): arrange Overview body into a balanced dashboard
Re-arrange everything below the two top cards so it reads as a dashboard:
- Insight band grouping the coach pacing line + anomaly callouts.
- Analytics row: Cost-per-outcome | Routing what-if (equal 2-up).
- Height-balanced 2-column body — Left: Models table + Most expensive
  sessions; Right: Top activities + Daily spend — so the left column no
  longer dead-ends into a big empty gap.
- Consistent gaps, align-items:start, single-column reflow under 900px.
typecheck clean, 88 tests pass.
2026-07-11 12:00:33 -07:00
iamtoruk
a50389b076 style(app): Overview into divided 3-column cards + metallic grade medal
- Top card: one card, three equal columns split by dividers —
  current spend (with Saved-to-date) | daily activity | efficiency
  scorecard. Removed the duplicate one-shot/cache ratings column and the
  separate efficiency card (EfficiencyScorecard + FuelRing gain a 'bare'
  mode).
- Stats: one card, three equal divided columns — Month-to-date |
  Projected | Nearest-limit fuel ring.
- Efficiency grade is now a circular brushed-metal medal (one subtle
  gradient + inset highlight + depth shadow), tiered A/B-C/D/F, tokenized
  for light+dark — premium, not the flat AI-looking pill.
- Tightened spacing/dead space; 1-column reflow under 900px.
typecheck clean, 88 tests pass.
2026-07-11 11:47:30 -07:00