Commit graph

8 commits

Author SHA1 Message Date
Resham Joshi
8fb478904e
feat: group a deleted sibling clone with its repository by folder name (#1692)
* feat(app): transient project selector in the desktop top bar

Scopes every report to one project path for the current session through
the same narrowing the VS Code workspace scope uses. The saved project
filter is never written; a restart is back on all projects.

* fix(app): let the name pickers ellipsize before the top bar overflows

* fix: key the daily project split per real path, not per label

A project label can span several paths: every Claude session started from the
home folder shares -Users-<name>, while its cwd puts it in a different project.
The day rollup keyed by label kept the first path it saw, so a rooted
--project headline took the whole label bucket on some days and missed it on
others. Key per (label, path), read old label-keyed days unchanged, bump the
daily cache to v68.

* fix(app): list only selectable, non-zero projects in the top bar picker

Rows with no session cwd (the Cursor and Grok Bot imports, provider label
fallbacks) report their label as the path, so the picker offered names the
bridge refuses and dash-to-slash guesses like eywa/lab. Keep only absolute
paths, and drop rows whose lifetime cost rounds to $0.00. The old test that
rooted a slashless Codex path is replaced: Codex now records the absolute cwd,
and a slashless path is always the label fallback.

* fix(app): show a toast when a project pick is refused

The pick was dropped silently. Also pin that an encoded -Users-... label
never reaches the picker list.

* feat: group projects by git repository

Clones and worktrees that share an origin remote are one project in every
project list (report JSON, By Project, the top bar picker, the terminal
dashboard), with the checkouts listed under the row. A --project path inside
a checkout scopes to the whole repository; --exact-project keeps the old
single-folder match. The origin is learned while the folder exists, kept in
git-origins.json and on each day's project entry (daily cache v69), so a
deleted scratch clone stays in its repository. The report JSON project list
now comes from the durable day set, so a row costs what selecting it reports.

* fix: a picked project row selects that row exactly

The top bar pick sends =<path>: a checkout in a repository still selects the
whole repository, and a plain folder selects itself without the folders under
it, which are rows of their own. So the picker row and the headline it opens
are the same set. Typed --project paths keep their prefix match. Days adopted
from an older cache learn the origin of every checkout that still exists, and
lookups outside a parse are saved on exit.

* fix: key the status snapshot on --exact-project

* feat: one Temporary folders row for scratch folders outside a repository

Every project path under a temp root (os.tmpdir(), /tmp, /var/folders and
their /private spellings) that is not a checkout of a known repository folds
into one row, in the report JSON, By Project, the terminal dashboard, the top
bar picker and the Settings Projects pane. Its pattern @temp selects or hides
all of them. The Projects pane leaves out rows that round to $0.00 unless a
saved pattern names them, and a saved pattern naming any checkout of a
repository row now names the whole row in the renderer too.

* feat: group a deleted sibling folder with its repository by folder name

A deleted folder with no recorded origin, named <checkout>-<suffix> or
<checkout>_<suffix> next to exactly one repository's checkout, joins that
repository row. The checkout entry carries matchedByFolderName and the Spend
drill-down labels it. Existing folders and names two repositories could claim
are left alone.

* feat: the longest checkout name wins a folder-name match

A deleted folder that fits several sibling checkouts joins the one with the
longest name; it is skipped only when the longest fits belong to two
repositories. The report project type declares matchedByFolderName.

* fix(claude): attribute each call to the project of its own cwd

* test: fold the temp roots the tests set, compare checkout ids as identities (Windows)

* test: compare folder-name checkout ids as identities (Windows)

* fix(claude): keep subfolder cwds in their project, count and show a split session once

* fix: an exclude path hides that checkout only, repository rows count a session once

* feat: mark folder-name matches in the CLI report and the project picker

* fix(claude): a deleted subfolder stays its own project
2026-10-08 06:54:41 +02:00
Resham Joshi
75d557e87b
feat(app): transient project selector in the desktop top bar (#1680)
Some checks are pending
CI / semgrep (push) Waiting to run
macOS Menubar CI / test (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Tests / test (24) (push) Waiting to run
Tests / test (26) (push) Waiting to run
Tests / test-platforms (macos-latest) (push) Waiting to run
Tests / test-platforms (windows-latest) (push) Waiting to run
Tests / desktop (push) Waiting to run
Tests / vscode (push) Waiting to run
Windows Menubar CI / check (ubuntu-latest) (push) Waiting to run
Windows Menubar CI / check (windows-latest) (push) Waiting to run
* feat(app): transient project selector in the desktop top bar

Scopes every report to one project path for the current session through
the same narrowing the VS Code workspace scope uses. The saved project
filter is never written; a restart is back on all projects.

* fix(app): let the name pickers ellipsize before the top bar overflows

* fix(app): list only selectable, non-zero projects in the top bar picker

Rows with no session cwd (the Cursor and Grok Bot imports, provider label
fallbacks) report their label as the path, so the picker offered names the
bridge refuses and dash-to-slash guesses like eywa/lab. Keep only absolute
paths, and drop rows whose lifetime cost rounds to $0.00. The old test that
rooted a slashless Codex path is replaced: Codex now records the absolute cwd,
and a slashless path is always the label fallback.

* fix(app): show a toast when a project pick is refused

The pick was dropped silently. Also pin that an encoded -Users-... label
never reaches the picker list.
2026-10-08 00:51:31 +02:00
Resham Joshi
43608c2558
desktop and menu bar: six-language localization (#1493)
The desktop app and the macOS menu bar can now run in English, French, Japanese, Korean, Simplified Chinese and Traditional Chinese. A Language setting in the desktop Settings picks the language or follows the system, and the choice is shared so the menu bar switches with it, relaunching to pick up the language with a retry so a switch never leaves it down.

Every user-facing string in the desktop renderer goes through a local lookup table, 1,249 keys per language, no framework and no network. The menu bar gains four .lproj to join English and Simplified Chinese, 626 keys each. A completeness gate fails the build if any language is missing a key, in both the desktop catalogs and the menu bar coverage suite. Fonts fall back per language so Japanese, Korean and both Chinese render their correct glyph forms, and dates and numbers format for the locale with values unchanged. English output is byte-identical.

Strings produced by the CLI engine stay English until the CLI is localized, a separate effort. Validated live in all six languages: no missing text, no clipping, correct fonts, numbers unchanged, and the menu bar switching for every language.
2026-09-19 03:25:46 -07:00
Resham Joshi
3e9542d63d
Consistent period totals and incremental refresh (#1467)
Every period headline now comes from one aggregation, so switching period no longer mixes snapshots taken minutes apart and Lifetime can no longer read below the six months inside it.
Days preserved from expired transcripts count toward the totals, and the usage streak is one machine-wide number taken from the hydrated daily cache.
A provider-filtered hero reads the scoped payload instead of the unscoped one.
Anchored menus and popovers render through a portal, so the card anatomy no longer clips them after three rows.
The Spend by-branch picker groups checkouts by git origin, one entry per repository with the checkout count beside it.
On the performance side, serve invalidation is scoped to the day a changed file can hold, -shm writes are ignored, and the optimize detectors keep one range-independent scan record with ordered version events.
Memo budgets are set by retained bytes, and every serve response carries the generation and timestamp it was built from.
A 30D stay with appends drops from 7253ms to 2387ms and peak RSS from 2904MB to 1819MB.
No cache format changed. Payloads stay byte-identical except for periodTotals, streak and liveSessions.
2026-09-17 12:35:05 +02:00
Resham Joshi
d81a86dd02
Desktop: measured polish pass, new frame, card system, motion, onboarding (#1459)
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.
2026-09-16 19:48:33 +02: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
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
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