diff --git a/CHANGELOG.md b/CHANGELOG.md index ae93611..64533cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,29 @@ follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Changed + +- Shorten heatmap hover/focus totals to values such as `42.4B tokens` and + `1.6K sessions` across local and account history. Keep exact grouped counts + in tooltips and accessibility labels. Summary counts compact from 1,000. + +### Fixed + +- Show the running app bundle's version in Settings instead of the legacy + hardcoded version. Source previews omit the version when none is supplied. + +## [0.3.0-beta.1] - 2026-10-16 + +### Release + +- Publish Electron build 54 as an opt-in Apple silicon beta. Keep Swift 0.2.41 + as the stable download and preserve its updater and installer contracts. +- Replace old product screenshots with current synthetic Codex, Claude, notch + and Settings captures. Refresh download, source-choice and beta-limit docs. +- Disclose the cosmetic 0.2.41 Settings footer and incomplete native acceptance. + The signed build is unchanged; Antigravity/Grok desktop login is follow-up work. + + ### Added - Label the Grok integration **Beta** in usage cards, setup and provider settings. @@ -32,7 +55,7 @@ follows [Semantic Versioning](https://semver.org/). ### Changed - Make buttons smaller throughout the Electron menu, Settings and side-notch - cards. Reveal the notch Settings gear on strip hover or keyboard focus. + cards. Keep Settings in each card footer and remove the duplicate strip gear. - Align heatmap menu labels with a fixed selection-checkmark column. - Keep Service status at the top of the Electron menu. Switch between provider @@ -47,7 +70,7 @@ follows [Semantic Versioning](https://semver.org/). summaries and setup details under Configure. Keep existing settings, account controls and explicit connection actions. Show the current source, collection state and reading availability separately. -- Place service health after usage cards. Use exact grouped heatmap totals on +- Keep service health above usage cards. Use exact grouped heatmap totals on hover and focus, with one Tab entry per heatmap and arrow-key navigation. Keep control focus visible without highlighting the panel edge. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88bf8ab..a5c05f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -136,9 +136,11 @@ See [`docs/DEMO.md`](docs/DEMO.md) for the data and privacy rules. ## TypeScript Electron candidate -The source version is 0.2.41. This candidate is unpublished; private build and -installation receipts exist, while complete native acceptance remains open. -See the [candidate guide](docs/electron/README.md) for source choices and parity gaps. +The source manifest retains 0.2.41. The public Electron beta is 0.3.0-beta.1, +build 54, with the version explicitly set during packaging. See the +[beta guide](docs/BETA.md) for the signed artifact, known cosmetic footer issue +and incomplete native acceptance, and the [Electron guide](docs/electron/README.md) +for source choices and parity gaps. Use the Node and pnpm versions declared in `package.json`. Provider accounts @@ -178,7 +180,8 @@ pnpm package:mac `package:mac` exports the existing SF Symbol stand-ins, builds the app and packages an Apple silicon ZIP with publication disabled. It does not install -or replace an app. Do not upload this candidate as the current public release. +or replace an app. Locally packaged output is not the verified public beta +artifact. Keep the stable release and beta download routes separate. Windows packaging, CI, native testing and release remain paused. electron-builder resolves `pnpm` from PATH when collecting production diff --git a/README.md b/README.md index 64b06fc..b6cd8ff 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,39 @@ MeterUsage monitors AI coding allowance and usage in a macOS menu bar and floating side notch. Electron is the active development target. The legacy Swift app remains the stable download and rollback option during migration. -## Choose an edition +## Download | Edition | Current status | Setup and behavior | | --- | --- | --- | -| Electron 0.2.41 source candidate | Active feature development; unpublished; native acceptance is incomplete | [Electron guide](docs/electron/README.md) | +| Electron 0.3.0-beta.1, build 54 | Public beta for Apple silicon, macOS 13+; signed and notarized | [Download beta](https://github.com/pekth/meterusage/releases/tag/v0.3.0-beta.1) · [Beta guide](docs/BETA.md) | | Legacy Swift 0.2.41 | Frozen for new features; published stable download and migration rollback | [Swift guide](docs/swift/README.md) | -Both implementations are already in this repository. The shared version number -does not make the candidate the published download, and it is not a released -v2.1. New contributions target Electron. Swift receives critical maintenance -only until verified cutover. See the [staged retirement decision](docs/adr/0024-electron-primary-swift-retirement.md) -and [version strategy](docs/VERSIONS.md). +Electron is the active app for new features. Swift remains the stable download +and rollback reference. The beta is installed manually; the stable updater does +not offer it. Quit the existing MeterUsage before replacing it, and keep a copy +for rollback. Both apps share their identity and saved data, so do not run them +together against a normal profile. See [beta installation and limits](docs/BETA.md) +and the [version strategy](docs/VERSIONS.md). -## Download and installation +## Current beta UI + +

+ Electron beta Claude usage demo with service status and token heatmap + Electron beta hover card and compact side-notch usage rail +

+ +Synthetic demo captures of build 54's renderer. Claude Desktop provides allowance, +not a chat-history heatmap. See the [full screenshot gallery and capture details](docs/SCREENSHOTS.md) +for Codex, Claude, side notch and Settings. + +The beta includes Codex browser sign-in/account history, optional Claude Desktop +allowance, existing CLI sources, compact provider tabs and hover cards, provider +reordering and readable token totals. Account coverage depends on the source. +Grok remains Beta and CLI-only; Antigravity desktop login is follow-up work. +Build 54's Settings footer incorrectly says `0.2.41`; the packaged version is +`0.3.0-beta.1`. See [known beta limits](docs/BETA.md#known-limits). + +## Stable Swift installation The default installation is the published Swift app for Apple silicon Macs with macOS 13 or later. No Swift, Xcode, Command Line Tools or Git is required. @@ -68,8 +87,10 @@ there is no automatic cutover. See [ADR 0010](docs/adr/0010-macos-typescript-ele ## Documentation -- [Swift guide and older demo screenshots](docs/swift/README.md) -- [Electron candidate guide and known gaps](docs/electron/README.md) +- [Beta download, verification and limits](docs/BETA.md) +- [Current beta screenshots](docs/SCREENSHOTS.md) +- [Swift guide](docs/swift/README.md) +- [Electron guide and known gaps](docs/electron/README.md) - [Versions and staged Swift retirement](docs/VERSIONS.md) - [Contributing](CONTRIBUTING.md) - [Project knowledge](docs/KB.md) and [decision index](docs/adr/README.md) @@ -79,8 +100,11 @@ there is no automatic cutover. See [ADR 0010](docs/adr/0010-macos-typescript-ele ## Compatibility links -Older root README links continue below. Detailed Swift descriptions and images -now live in the Swift guide. +Older root README links continue below. Detailed Swift descriptions remain in +the Swift guide; product screenshots now show the Electron beta. + + +See [downloads](#download). See the [Electron candidate guide](docs/electron/README.md). @@ -103,7 +127,7 @@ See the [Swift Agent Budget CLI](docs/swift/README.md#agent-budget-cli) and -See [older Swift demo screenshots](docs/swift/README.md#showcase). +See [current beta screenshots](docs/SCREENSHOTS.md). See the [Swift provider matrix](docs/swift/README.md#provider-matrix). @@ -118,7 +142,7 @@ See [privacy and data handling](docs/PRIVACY.md). See [installation details](docs/swift/README.md#download-and-installation). -See [edition choice](#choose-an-edition). +See [downloads](#download). ## License and disclaimer diff --git a/assets/showcase-codex-reset.png b/assets/showcase-codex-reset.png deleted file mode 100644 index 684512d..0000000 Binary files a/assets/showcase-codex-reset.png and /dev/null differ diff --git a/assets/showcase-menubar.png b/assets/showcase-menubar.png deleted file mode 100644 index c7ebfc3..0000000 Binary files a/assets/showcase-menubar.png and /dev/null differ diff --git a/assets/showcase-popover.png b/assets/showcase-popover.png deleted file mode 100644 index cc71263..0000000 Binary files a/assets/showcase-popover.png and /dev/null differ diff --git a/assets/showcase-sidenotch.png b/assets/showcase-sidenotch.png deleted file mode 100644 index d8aecdb..0000000 Binary files a/assets/showcase-sidenotch.png and /dev/null differ diff --git a/docs/BETA.md b/docs/BETA.md new file mode 100644 index 0000000..911cfd7 --- /dev/null +++ b/docs/BETA.md @@ -0,0 +1,84 @@ +# Electron public beta + +MeterUsage **0.3.0-beta.1, build 54** is an opt-in macOS beta. The ZIP supports +Apple silicon and macOS 13 or later. It is Developer ID signed, notarized and +stapled. Swift `0.2.41` remains the stable release and updater download. + +## Install + +1. Download [the beta ZIP](https://github.com/pekth/meterusage/releases/download/v0.3.0-beta.1/MeterUsage-0.3.0-beta.1-mac-arm64.zip) + and [SHA256SUMS](https://github.com/pekth/meterusage/releases/download/v0.3.0-beta.1/SHA256SUMS). +2. Quit MeterUsage and keep a copy of your existing app and its saved data for + rollback. The beta shares the app identity and data locations with Swift. +3. Unzip the download, move `MeterUsage.app` to Applications, and open it. +4. In Settings, choose an existing CLI source or an optional account connection. + Selecting a method alone does not connect it. Use its connection button. + +No Node, pnpm, Swift or Xcode installation is needed to use this prebuilt beta. +Do not run the two editions together against a normal profile. Updating to later +betas is manual. `Scripts/make-app.sh` still prepares the stable Swift download. + +To check the ZIP in its download directory: + +```sh +shasum -a 256 -c SHA256SUMS +``` + +Expected ZIP SHA-256: + +```text +ec4e8878e6a35120b397d8c118d023f010e8a6952a257b75764f05ecbc6f52ba +``` + +## Provider coverage + +- Codex browser sign-in reads allowance and provider-reported daily account + tokens when supplied. Missing days remain unavailable. Its account history + is separate from this Mac's CLI sessions; universal product/device coverage + is not established. +- Codex CLI reads existing authenticated quota and local rollout activity. +- Claude Desktop uses explicit consent and a macOS Keychain access prompt to + read current allowance through a compatibility endpoint. Some accounts, + including free accounts, may have no allowance token. This does not sign the + user out of Claude Desktop. Desktop chat/token history is not collected. +- Claude Code CLI reads local transcript activity. Quota requires an optional + companion file. Local activity does not cover other devices. +- Existing Grok CLI, Antigravity container, OpenCode Go and other adapters retain + their source-specific limits. New Antigravity Desktop and Grok/Cursor login + integrations are not included. Grok consumer web sign-in is unavailable. + +See [source choices](electron/README.md#source-choices), +[desktop connections](DESKTOP-CONNECTIONS.md) and [privacy](PRIVACY.md). + +## Known limits + +- Settings displays a hardcoded `0.2.41` footer. The installed bundle and beta + download are version `0.3.0-beta.1`, build 54. A footer fix is separate work. +- Native keyboard/VoiceOver, complete setup/account/offline journeys, + notifications/login items, sharing, update/relaunch/rollback and multi-display + notch transitions have incomplete acceptance coverage. These are gaps in + verification, not confirmed failures. +- Live Codex refresh was observed. Claude paid-account allowance was not + live-tested. Optional support-report delivery and deployed relay limits + were not verified end to end. +- Windows support is paused. Intel Mac binaries are not provided by this beta. + +Use Settings > Diagnostics for sanitized diagnostics and **Send report**. +The app limits report attempts to one every ten minutes, including relaunches. +Review the [report contents and privacy limits](REPORTING.md) before sending. +Never include tokens, passwords or raw account files in an issue. + +## Build and screenshot identity + +The signed artifact was built from source +`6ce695324e54bb09bf6e5ab36fd4733875c94427`. Release documentation and new +screenshots are a documentation-only descendant; executable inputs are unchanged. +Source manifests retain `0.2.41`; beta packaging explicitly set `0.3.0-beta.1`. + +Matching validation recorded 417 synthetic tests across 19 files, TypeScript +checking and production bundling. Signing, notarization, stapling, Gatekeeper, +installed bundle identity and ZIP bytes were verified separately. These checks +do not close the native acceptance gaps above. + +The [screenshot gallery](SCREENSHOTS.md) uses invented demo data and the actual +release renderer. It replaces old product captures without exposing real accounts. diff --git a/docs/DEMO.md b/docs/DEMO.md index 000c6e2..617fa87 100644 --- a/docs/DEMO.md +++ b/docs/DEMO.md @@ -1,98 +1,69 @@ # Demo mode -Demo mode runs the real app against synthetic, invented data. +Demo mode uses the real app views with invented data. Use it for screenshots +and UI inspection without exposing real accounts. The [current beta gallery](SCREENSHOTS.md) +shows Electron build 54. Old product captures have been removed. ---- +## Electron browser preview -### ⚡ TL;DR +From the beta source checkout, after installing its declared dependencies: -* 🎭 **True-to-Life UI**: Runs the real views, formatting, and pacing calculations without touching real accounts, credentials, or network services. -* 📸 **Safe for Screenshots**: Ideal for documentation and public sharing without leaking private spend or account tiers. -* 🚀 **One-Command Launch**: - ```sh - METERUSAGE_DEMO=1 swift run meterusage - ``` - *(A **Demo** badge in the header ensures synthetic numbers are never confused with real ones).* - ---- - -## Why Demo Mode Exists - -Screenshots — for the README, an issue report, or a blog post — and contributor testing should never require a real account, a signed-in CLI, or exposing personal usage. A maintainer's real popover shows real spend, real session counts, and a real plan tier; none of that belongs in a public repository. +```sh +node_modules/.bin/vp dev --mode demo --host 127.0.0.1 +``` -## Launching Demo Mode +Open the printed address with `?surface=flyout`, `?surface=settings` or +`?surface=notch`. This dedicated demo mode uses the actual renderer and an +in-memory preview bridge. It has no native IPC, provider access or live network +transport. Native-only actions return an error. It does not validate Keychain, +window positioning, login items, sharing or other macOS behavior. -The app is a bundle, and macOS `open` does **not** forward shell environment variables to bundled apps by default. Use `--env` to pass it: +Provider marks exported during macOS packaging are needed for every icon to +render. Use those matching generated resources when capturing on Linux. Do not +substitute unrelated marks. Browser fonts and framing can differ from macOS. -```sh -open -a MeterUsage --env METERUSAGE_DEMO=1 -``` +## Electron candidate -Or execute the binary directly from the terminal: +After `pnpm build`, synthetic headless inspection uses: ```sh -METERUSAGE_DEMO=1 /Applications/MeterUsage.app/Contents/MacOS/MeterUsage +meterusage_demo_root="$(node -p 'require("node:fs").realpathSync(require("node:os").tmpdir())')" +node dist/main/cli.cjs json --demo --candidate-profile "$meterusage_demo_root/meterusage-cli-demo" ``` -From a local source checkout: +For a packaged app on macOS, execute its binary directly: ```sh -METERUSAGE_DEMO=1 swift run meterusage +./release/mac-arm64/MeterUsage.app/Contents/MacOS/meterusage --demo --candidate-profile /private/tmp/meterusage-native-demo ``` -Quit and relaunch normally without the variable to return to your real data. - -## What It Is — and Isn't - -It is **not** a mock UI. The production SwiftUI views, AppCoordinator, formatters, and pacing calculations run identically to normal execution. Only the data sources are swapped: real sources inspect local CLIs and session files, while demo sources return fixed, synthetic values from memory. +The profile must be empty on first use and use a canonical path. Symlink aliases, +including macOS `/tmp` and `/var`, are rejected. Reuse the same marked profile +for relaunch checks. Preferences, caches, history and Electron state stay there. +Live provider discovery, login-item changes and update installation are disabled. +Demo reset confirmation consumes only synthetic fixture credits. -## Synthetic Data Breakdown +## Swift legacy demo -All demo data is invented and deterministic: - -- **Service Health**: Reports operational components for Codex and Claude public status pages. -- **Codex Quota**: Plus plan tier, 5-hour and weekly allowance windows, a GPT-5.3-Codex-Spark window, two expiring full-reset credits, and an approximate credit balance. -- **Antigravity Quota**: Multi-group model families: - - *Gemini Models*: Weekly limit (89% used, 11% left) and 5-hour limit (9% used, 91% left). - - *Claude and GPT models*: Weekly limit (1% used, 99% left) and 5-hour limit (0% used, 100% left). -- **OpenRouter**: Synthetic monthly dollar spending limit, account balance meter, and 30-day token telemetry. -- **Grok**: Weekly allowance window with countdown and session activity history. -- **OpenCode Go**: 26 sessions, 492 messages, token volume totals, and estimated cost. -- **Claude**: Optional companion-file quota windows and tokens-per-day heatmap. -- **26-Week Heatmaps**: 26-week activity matrices for Codex (sessions) and Claude (tokens) with interactive daily, weekly, and cumulative views. - -Percentages span calm green and amber warning bands so screenshots demonstrate color headroom scales clearly without alarmist red styling. Reset countdowns are calculated relative to launch time so screenshots remain natural. - -## Safety Guarantees - -- **Strict Activation**: The environment variable must be exactly `1`. Values like `true`, `yes`, or `0` remain off. -- **Whole-App Integrity**: Evaluated once at composition root — the dashboard is never partially real and partially fake. -- **Prominent Badge**: A **Demo** badge is displayed in the header to prevent any confusion with live telemetry. -- **Synthetic sources**: Demo provider sources perform no network requests and invoke no provider CLI commands. The Electron candidate writes its own isolated preferences, history and cache files. - -## Electron candidate - -After `pnpm build`, run the candidate JSON path without a tray or window: +From the retained Swift source on macOS: ```sh -meterusage_demo_root="$(node -p 'require("node:fs").realpathSync(require("node:os").tmpdir())')" -node dist/main/cli.cjs json --demo --candidate-profile "$meterusage_demo_root/meterusage-cli-demo" +METERUSAGE_DEMO=1 swift run meterusage ``` -On macOS, launch the separate packaged candidate directly: +The environment value must be exactly `1`. Swift demo mode swaps provider +sources at composition; it does not establish current Electron appearance. +Quit and relaunch normally to return to real data. Do not run legacy and beta +normal profiles together. -```sh -./release/mac-arm64/MeterUsage.app/Contents/MacOS/meterusage --demo --candidate-profile /private/tmp/meterusage-native-demo -``` +## Data and proof + +Demo sources supply example allowances, reset windows, account balances and +local token/session activity. Local history and account allowance are separate +measurements in the real app. A Claude demo heatmap does not imply access to +Claude Desktop chat history. Missing provider-reported values stay unknown. -The explicit profile must be empty on first use and use a canonical path. -Candidate mode rejects symlink aliases, including macOS `/tmp` and `/var`. -Demo mode selects synthetic -providers before composition and isolates all preferences, caches, history and -Electron state. Relaunch with the same profile to test persistence. Live -provider discovery, login-item changes and update installation are disabled. -Demo reset confirmation consumes only synthetic fixture credits. The original -logo PNGs are reused; SF Symbol stand-ins are exported on macOS during packaging. - -Native candidate UI and two different-height notch captures remain required -acceptance evidence. Swift screenshots above do not prove Electron rendering. +Public captures must use synthetic data and identify it as demo data. Browser +captures prove renderer output only. Native anchoring acceptance still needs +Mac captures for two providers with different card heights, as defined in +[ADR 0004](adr/0004-side-notch-anchor-invariant.md). diff --git a/docs/KB.md b/docs/KB.md index 5971b1f..43cdaf5 100644 --- a/docs/KB.md +++ b/docs/KB.md @@ -1,6 +1,6 @@ # Project knowledge -Last source review: 2026-10-10 +Release documentation refresh: 2026-10-16; build 54 source review is unchanged. ## Edition documentation @@ -10,8 +10,10 @@ Last source review: 2026-10-10 native acceptance, deliberate cutover and retrievable archive checks pass. Source retirement is pending. Electron-required Swift helpers and assets stay. - [Swift 0.2.41](swift/README.md) is the published default install route. - [Electron source 0.2.41](electron/README.md) is an unpublished candidate with - private build/installation receipts and incomplete native acceptance. + [Electron 0.3.0-beta.1, build 54](BETA.md) is an opt-in public prerelease, + signed and notarized for Apple silicon. Native acceptance remains incomplete. + Source manifests and its cosmetic Settings footer still say 0.2.41; packaging + explicitly sets the beta version. Stable updaters do not offer the beta. - Both implementations remain in their current source trees. Shared app name, bundle ID and default data/preferences prevent safe normal-profile coexistence. Use only documented isolated QA profiles. @@ -27,7 +29,9 @@ Last source review: 2026-10-10 OpenCode Bun fallback discovery, pending AGY/OpenCode onboarding and unresolved notch-dimension/grid-geometry feedback remain distinct. See the candidate guide. - Edition documentation was reconciled against revision - `605f1c4328eed6d4f793fcee0937a8d2e1659f6d` and its accompanying candidate changes. + build source `6ce695324e54bb09bf6e5ab36fd4733875c94427`. +- Current product screenshots use build 54's renderer and synthetic demo data. + Old product captures are removed; see [capture provenance](SCREENSHOTS.md). ## Repository state @@ -37,6 +41,11 @@ Last source review: 2026-10-10 ## Product and source facts +- Heatmap hover/focus readouts use compact token and session totals, including + local, account, session-only, daily, weekly and cumulative grids. Exact grouped + counts remain in tooltips and accessible labels. Summary counts compact from + 1,000. This follow-up is separate from the published build 54 artifact. + - Onboarding and Settings offer provider-specific source choices: Codex browser sign-in or CLI, Claude Desktop or Claude Code CLI, and Grok CLI only. Choosing an option does not change collection until its action is used. Use CLI waits @@ -59,6 +68,9 @@ Last source review: 2026-10-10 Input accessible names and full label hit areas remain. Settings action errors retain their alert role, message and dismissal in a sticky banner. Advanced account rows wrap without changing their save/remove behavior. +- Settings displays the running Electron bundle version supplied by the main + process. Source previews without bundle metadata omit the version instead + of showing the legacy Swift version. - Candidate Usage keeps Service status first and visible, before provider usage. Provider/account tabs show one usage card at a time in saved provider order. Arrow keys and Home/End switch tabs; removing a selected slot falls back to @@ -86,13 +98,13 @@ Last source review: 2026-10-10 and keyboard focus remain immediate. Renderer fixture and build proof does not establish rendered layout, sticky alerts, motion or native export proof. -- Electron heatmaps show exact grouped counts and readable dates for daily, - weekly or cumulative scope on hover and keyboard focus, tooltips and accessible - labels. Each live heatmap has one Tab entry; arrow keys move between days and +- Electron heatmaps show compact counts and readable dates for daily, + weekly or cumulative scope on hover and keyboard focus. Tooltips and accessible + labels retain exact grouped counts. Each live heatmap has one Tab entry; arrow keys move between days and Home/End reach the first/last available cell. Tab leaves the heatmap. Shared images have no focusable cells. Account, local-token and session-only providers share the same heading, caption, cells and intensity legend. - Hover and keyboard focus replace the legend with the exact dated readout + Hover and keyboard focus replace the legend with the compact dated readout in a natural-height row. Long readouts can grow the card; the top and rail stay anchored. Unreported account days have subtle solid outlines; reported zeroes have filled neutral cells. Each source retains its own time range and units. Count-only providers report token totals unavailable. diff --git a/docs/SCREENSHOTS.md b/docs/SCREENSHOTS.md new file mode 100644 index 0000000..6da6874 --- /dev/null +++ b/docs/SCREENSHOTS.md @@ -0,0 +1,47 @@ +# Beta screenshots + +These captures show Electron **0.3.0-beta.1, build 54** using invented demo +data. They use the release renderer at source revision +`6ce695324e54bb09bf6e5ab36fd4733875c94427`, captured in a browser at 2x pixel +density. Required generated provider marks come from the signed release bundle. +Only unused canvas below the app is cropped. Browser fonts and window framing +can differ from macOS; these images do not prove native behavior. + +## Usage + +

+ Beta Codex demo showing service status, allowance and token heatmap + Beta Claude demo showing allowance and token heatmap +

+ +The demo includes local token activity. Claude Desktop supplies allowance only; +its sample heatmap does not imply Desktop chat-history access. Codex browser +sign-in supplies separate provider-reported account history when available. + +## Side notch + +

+ Beta Codex hover card and narrow provider usage rail + Beta Claude hover card with the same provider rail +

+ +Both browser captures retain the rail at x=220, y=0, width=36 CSS pixels while +the card height changes. This checks renderer geometry, not multi-display +positioning on a Mac. Details open on hover; the rail has one design and no pin. + +## Settings + +![Connections and optional onboarding](images/beta-settings-connections.png) + +![Appearance and panel sizing](images/beta-settings-appearance.png) + +Build 54's Settings footer still says `0.2.41`. The packaged app version is +`0.3.0-beta.1`; the footer is a known cosmetic issue in this beta. + +## Sharing + +![MeterUsage beta social preview](images/beta-social-preview.png) + +Older product screenshots have been removed from the current documentation. +App icons, provider marks and the explicitly labeled historical concept mockups +remain. Git history and earlier release archives retain their original files. diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 21e77f0..2a1fe5a 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -1,29 +1,29 @@ # MeterUsage versions and repository strategy -Electron is the sole active target for new features. The published Swift app is -frozen as legacy support and rollback during migration. Both currently say -`0.2.41`. That shared number does not establish -build identity, publication, installation or native acceptance. The candidate -is not a released v2.1. This comparison describes source at revision -`605f1c4328eed6d4f793fcee0937a8d2e1659f6d` with the accompanying candidate changes. +Electron is the sole active target for new features. Electron **0.3.0-beta.1, +build 54** is an opt-in public prerelease. Swift **0.2.41** remains the stable +download and rollback reference. The beta does not replace the stable updater. ## Current layout and status -| Area | Retained Swift | Electron candidate | +| Area | Retained Swift | Electron beta | | --- | --- | --- | -| Development | Critical fixes and transition compatibility only | All new features | -| Source and manifest | `Sources/`, `Tests/`, [Package.swift](../Package.swift) | `src/`, `tests/`, [package.json](../package.json) | -| Version | 0.2.41 in [Info.plist](../Resources/Info.plist) | 0.2.41 in package metadata | -| Build | `swift build`; explicit `./Scripts/make-app.sh --build-from-source` | `pnpm build`; macOS `pnpm package:mac` | -| Default user download | Pinned published Swift ZIP via `./Scripts/make-app.sh` | No published candidate download route | -| Build/install evidence | Separate from source and release metadata | Private build and installation receipts exist; complete native acceptance remains open | -| Guide | [Swift](swift/README.md) | [Electron](electron/README.md) | - -A source check does not prove a signed bundle. A built bundle does not prove -installation or launch. A tag does not create a GitHub Release, and an uploaded -asset does not prove native runtime acceptance. Current public availability and -installed state need their own observation; this documentation does not refresh -them. The pinned Swift download contract remains unchanged. +| Development | Critical fixes and transition compatibility | All new features | +| Source | `Sources/`, `Tests/`, `Package.swift` | `src/`, `tests/`, `package.json` | +| Download version | 0.2.41 | 0.3.0-beta.1, build 54 | +| Source metadata | 0.2.41 | Manifest retains 0.2.41; packaging overrides it | +| User installation | Pinned Swift ZIP via `Scripts/make-app.sh` | Manual signed/notarized Apple silicon ZIP | +| Updates | Stable release only | Manual beta downloads; no beta updater | +| Guide | [Swift](swift/README.md) | [Beta installation and limits](BETA.md), [Electron](electron/README.md) | + +Build 54 was built from `6ce695324e54bb09bf6e5ab36fd4733875c94427`. +The beta tag also carries refreshed documentation and screenshots; its +executable inputs are unchanged. Its Settings footer still incorrectly displays +`0.2.41`. See [known limits and verification](BETA.md#known-limits). + +The beta source and signed bundle have separate recorded checks. Native +acceptance remains incomplete and is disclosed for this opt-in prerelease. +Stable cutover, stack merge and Swift source retirement remain separate work. ## Repository coexistence and app isolation @@ -59,7 +59,7 @@ data compatibility, native acceptance and no-automatic-cutover rules still apply performance. Keep intentional UI removals intact. 3. Verify deliberate update and rollback before public cutover. Preserve the current Swift download and installer contract until the replacement is ready. - Release publication remains subject to the existing release holds. + The opt-in beta is published separately; stable cutover still requires these checks. 4. Preserve the retrievable legacy source revision and matching release artifact, then remove old-app-only source, tests, build steps and obsolete CI references from the active branch in a reviewed change. No second repository is needed. @@ -69,8 +69,8 @@ Keep `Scripts/electron/desktop-keychain.swift`, fixtures/contracts used by Electron. Trace dependencies before removing legacy files. Retiring the Swift application does not mean deleting all Swift files. -Only the development guidance is applied here. Source removal, workflow changes, -version assignment and cutover remain pending. +Source removal, workflow changes and stable cutover remain pending. Publishing +the manual beta does not change the stable download contract. ### Update-channel prerequisites diff --git a/docs/electron/README.md b/docs/electron/README.md index 0e8795d..8e0766d 100644 --- a/docs/electron/README.md +++ b/docs/electron/README.md @@ -1,14 +1,16 @@ -# MeterUsage Electron candidate +# MeterUsage Electron beta Electron is the sole active target for new MeterUsage features. The legacy Swift app is frozen as the current stable download and rollback option until [staged retirement](../adr/0024-electron-primary-swift-retirement.md) passes. -This is the unpublished macOS TypeScript/Electron/React candidate. Its source -version is `0.2.41` in [package.json](../../package.json), matching the retained -Swift version number. Private build and installation receipts exist. Complete -native acceptance and public cutover remain open; this is not a released v2.1. -Use the [published Swift app](../swift/README.md) for the default installation. +The public beta is **0.3.0-beta.1, build 54**, for Apple silicon and macOS 13+. +Use the [beta download and installation guide](../BETA.md). The ZIP is Developer +ID signed and notarized. This is an opt-in manual download; Swift stays stable. +Source manifests and build 54's cosmetic Settings footer still say `0.2.41`. +Packaging explicitly sets the beta version. Full native acceptance remains open. + +See the [current beta screenshots](../SCREENSHOTS.md) for Usage, notch and Settings. ## Source choices @@ -21,7 +23,7 @@ Usage. See [ADR 0023](../adr/0023-onboarding-source-choice.md). | --- | --- | --- | | Codex browser sign-in | Existing official app helper, with command-install compatibility, signs in through the provider page using a separate keyring-only profile. Reads allowance and provider-reported daily account tokens. | Account history is separate from CLI tokens. No local sessions, category split, cost or burn attribution comes from this method. Universal device/product coverage is unverified. | | Codex CLI | Existing authenticated CLI supplies quota/reset metadata; local rollouts supply tokens, sessions and estimated cost. | Activity on this Mac only. | -| Claude Desktop | Explicit consent permits a fixed Keychain helper and read-only Desktop cache/cookie access for current account allowance. | Allowance only. No Desktop chat/token history, local cost, tier or extra-usage credit projection. Endpoint compatibility and live acceptance remain open. | +| Claude Desktop | Explicit consent permits a fixed Keychain helper and read-only Desktop cache/cookie access for current account allowance. | Allowance only. No Desktop chat/token history, local cost, tier or extra-usage credit projection. Compatibility can fail when the active account has no allowance token; paid-account live acceptance remains open. | | Claude Code CLI | Local transcript counters, tier metadata and optional companion quota files. | Local activity; allowance requires the [companion](../COMPANION.md). | | Grok CLI (Beta) | Existing CLI authentication and local session summaries. | Consumer website/mobile sign-in is unsupported. Session/message counts do not establish token totals. | @@ -117,10 +119,12 @@ its token or session units. The optional 30-day line chart is separate. Connecte Codex has a daily 26-week account-token grid ending on the latest provider date. Unreported dates stay unavailable; reported zero is zero. Account history stays in memory and is excluded from local rollups, durable history and schema-1 JSON. -Live availability and coverage remain unverified. See +Live Codex account history has been observed; complete device/product coverage remains unverified. See [ADR 0022](../adr/0022-codex-account-token-history.md). -Hover and keyboard focus expose dated exact grouped counts. Each live heatmap +Compact hover/focus readouts are an unreleased source change; published build 54 +retains exact readouts. Hover and keyboard focus show dated compact totals such as `42.4B tokens` and +`1.6K sessions`. Tooltips and accessible labels retain exact grouped counts. Each live heatmap has one Tab entry. Use arrow keys to move between days, Home/End for the first or last cell, and Tab to leave the grid. Shared images omit focusable cells. Feedback about 30-day versus 26-week grid geometry @@ -211,7 +215,7 @@ implementation, packaging, CI, testing and release remain paused. reference and rollback path. No automatic cutover, data migration, release or installed-app replacement follows from these docs. The [repository strategy](../VERSIONS.md#proposed-same-repository-strategy) records the accepted development direction and the checks still required for -source retirement and release. Electron's required Swift helpers remain. +source retirement and stable cutover. This opt-in beta does not close those checks. Electron's required Swift helpers remain. Desktop account research for Grok/Cursor, Antigravity and OpenCode Go is recorded in [DESKTOP-LOGIN-RESEARCH.md](../DESKTOP-LOGIN-RESEARCH.md). It separates diff --git a/docs/images/beta-notch-claude.png b/docs/images/beta-notch-claude.png new file mode 100644 index 0000000..c8c37f9 Binary files /dev/null and b/docs/images/beta-notch-claude.png differ diff --git a/docs/images/beta-notch-codex.png b/docs/images/beta-notch-codex.png new file mode 100644 index 0000000..ab688e9 Binary files /dev/null and b/docs/images/beta-notch-codex.png differ diff --git a/docs/images/beta-settings-appearance.png b/docs/images/beta-settings-appearance.png new file mode 100644 index 0000000..f5330ca Binary files /dev/null and b/docs/images/beta-settings-appearance.png differ diff --git a/docs/images/beta-settings-connections.png b/docs/images/beta-settings-connections.png new file mode 100644 index 0000000..0e5f4e4 Binary files /dev/null and b/docs/images/beta-settings-connections.png differ diff --git a/docs/images/beta-social-preview.png b/docs/images/beta-social-preview.png new file mode 100644 index 0000000..33039b6 Binary files /dev/null and b/docs/images/beta-social-preview.png differ diff --git a/docs/images/beta-usage-claude.png b/docs/images/beta-usage-claude.png new file mode 100644 index 0000000..c09614e Binary files /dev/null and b/docs/images/beta-usage-claude.png differ diff --git a/docs/images/beta-usage-codex.png b/docs/images/beta-usage-codex.png new file mode 100644 index 0000000..01a95a7 Binary files /dev/null and b/docs/images/beta-usage-codex.png differ diff --git a/docs/images/menubar.png b/docs/images/menubar.png deleted file mode 100644 index c7ebfc3..0000000 Binary files a/docs/images/menubar.png and /dev/null differ diff --git a/docs/images/popover-2.png b/docs/images/popover-2.png deleted file mode 100644 index c82a546..0000000 Binary files a/docs/images/popover-2.png and /dev/null differ diff --git a/docs/images/popover-3.png b/docs/images/popover-3.png deleted file mode 100644 index b4a4174..0000000 Binary files a/docs/images/popover-3.png and /dev/null differ diff --git a/docs/images/popover.png b/docs/images/popover.png deleted file mode 100644 index 7d16201..0000000 Binary files a/docs/images/popover.png and /dev/null differ diff --git a/docs/images/settings-accounts.png b/docs/images/settings-accounts.png deleted file mode 100644 index 4dbcd4c..0000000 Binary files a/docs/images/settings-accounts.png and /dev/null differ diff --git a/docs/images/settings-providers.png b/docs/images/settings-providers.png deleted file mode 100644 index a11318b..0000000 Binary files a/docs/images/settings-providers.png and /dev/null differ diff --git a/docs/images/sidenotch-codex.png b/docs/images/sidenotch-codex.png deleted file mode 100644 index 200febf..0000000 Binary files a/docs/images/sidenotch-codex.png and /dev/null differ diff --git a/docs/images/sidenotch-grok.png b/docs/images/sidenotch-grok.png deleted file mode 100644 index 3e4bfbd..0000000 Binary files a/docs/images/sidenotch-grok.png and /dev/null differ diff --git a/docs/images/sidenotch-opencode-go.png b/docs/images/sidenotch-opencode-go.png deleted file mode 100644 index 43be559..0000000 Binary files a/docs/images/sidenotch-opencode-go.png and /dev/null differ diff --git a/docs/images/sidenotch-v2-live.png b/docs/images/sidenotch-v2-live.png deleted file mode 100644 index b9f1bdb..0000000 Binary files a/docs/images/sidenotch-v2-live.png and /dev/null differ diff --git a/docs/images/sidenotch.png b/docs/images/sidenotch.png deleted file mode 100644 index d8aecdb..0000000 Binary files a/docs/images/sidenotch.png and /dev/null differ diff --git a/docs/images/social-preview.png b/docs/images/social-preview.png deleted file mode 100644 index a957180..0000000 Binary files a/docs/images/social-preview.png and /dev/null differ diff --git a/docs/swift/README.md b/docs/swift/README.md index 2b115e8..ec75a26 100644 --- a/docs/swift/README.md +++ b/docs/swift/README.md @@ -26,8 +26,8 @@ Swift receives only critical fixes and transition compatibility work. Keep this release as the stable download and rollback until the [retirement conditions](../adr/0024-electron-primary-swift-retirement.md#retirement-conditions) pass. Removing the old app source later will preserve its retrievable history. -The screenshots below are older synthetic Swift demo captures. They are not -Electron screenshots or evidence of the current candidate UI. +Current product images are in the [Electron beta gallery](../SCREENSHOTS.md). +Old Swift product captures have been removed from the active documentation. MeterUsage shows supported AI coding allowances, measured token usage, pacing and reset times in the menu bar, popover and floating side notch. @@ -50,42 +50,8 @@ See [installation](#download-and-installation) for first-launch steps. ## Showcase -### Ambient Time-To-Empty & Side Notch HUD -A dockable, collapsible HUD pinned to the edge of your screen. Hovering any provider ring expands a dedicated detail card with rate limits, ambient time-to-empty, pacing diagnostics, and token telemetry. The panel is anchored by the strip's top-right corner, so switching providers never moves the strip ([`docs/SIDE-NOTCH.md`](../../docs/SIDE-NOTCH.md)): - -

- Codex rate limits, reset credits, and usage -      - Antigravity limits, pacing, and token telemetry -

- -

- Grok weekly quota, sessions, and message volume -      - OpenCode Go rolling, weekly, and monthly quotas -

- -### Menu Bar & Popover Dashboard -Click the menu bar mark anytime to inspect full rate-limit details, multi-window countdowns, and 26-week activity heatmaps: - -

- Service status, pipeline, AI coding today, and Codex quotas -   - Codex heatmap, Grok quota, and OpenCode Go quota -   - OpenRouter quota, Claude quota, Claude heatmap, and Antigravity -

- -Screenshots show the app in demo mode (`METERUSAGE_DEMO=1`). All numbers and names are synthetic. - -### Settings: Accounts & Providers -Toggle providers on or off, choose refresh cadence, and switch themes and the accent colour: - -

- Settings accounts list with provider connections and appearance theme -      - Settings providers list with refresh interval and side notch toggles -

+See the [current Electron beta screenshots](../SCREENSHOTS.md). This legacy +Swift guide retains its behavior and installation reference without old images. --- diff --git a/src/main/electron.ts b/src/main/electron.ts index 4bd12a7..79a25a0 100644 --- a/src/main/electron.ts +++ b/src/main/electron.ts @@ -79,7 +79,7 @@ async function start(launch: Launch) { tray.on("right-click", () => tray.popUpContextMenu(Menu.buildFromTemplate([{ label: "Open MeterUsage", click: () => showUsage() }, { label: "Settings…", click: () => showSettings() }, { type: "separator" }, { label: "Quit MeterUsage", role: "quit" }]))); tray.on("click", () => { const w = windows.get("flyout"); w?.isVisible() ? w.hide() : showUsage(); }); let shareState: ViewState | undefined, shareHeight = 0, shareReady: ((image: Electron.NativeImage) => void) | undefined, shareFailed: ((error: Error) => void) | undefined; - const state = (surface?: Surface): ViewState => surface === "share" && shareState ? shareState : ({ ...(surface === "settings" ? { issueReport: issueReports.state } : {}), connections: connections.state, snapshot: coordinator.snapshot(), settings: projectSettings(prefs.values, prefs.accounts, launch.home, surface), systemDark: nativeTheme.shouldUseDarkColorsForSystemIntegratedUI, notch: { ...notch, maxHeight: Math.floor((anchor ? screen.getDisplayNearestPoint(anchor) : screen.getPrimaryDisplay()).workArea.height) }, update: updater.visible ? { version: updater.visible.version, state: updater.installState } : undefined }); + const state = (surface?: Surface): ViewState => surface === "share" && shareState ? shareState : ({ appVersion: app.getVersion(), ...(surface === "settings" ? { issueReport: issueReports.state } : {}), connections: connections.state, snapshot: coordinator.snapshot(), settings: projectSettings(prefs.values, prefs.accounts, launch.home, surface), systemDark: nativeTheme.shouldUseDarkColorsForSystemIntegratedUI, notch: { ...notch, maxHeight: Math.floor((anchor ? screen.getDisplayNearestPoint(anchor) : screen.getPrimaryDisplay()).workArea.height) }, update: updater.visible ? { version: updater.visible.version, state: updater.installState } : undefined }); const updater = new Updater(prefs, app.getVersion(), publish, undefined, release => { if (!launch.demo && !launch.candidate && Notification.isSupported()) new Notification({ title: "MeterUsage update available", body: `MeterUsage ${release.version} is ready to install.`, silent: true }).show(); }); diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 9df271e..01dfb26 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -11,7 +11,7 @@ import { pricingSnapshot } from "../domain/pricing"; const accents: Record = { blue: "#0a84ff", violet: "#a78bfa", teal: "#2dd4bf", amber: "#fbbf24", rose: "#fb7185", graphite: "#aeaeb2" }; const colours: Record = { codex: "var(--accent)", claude: "var(--claude)", antigravity: "var(--antigravity)", grok: "var(--grok)", openCodeGo: "var(--opencode)", openRouter: "var(--openrouter)", cursor: "#1ab3e6", copilot: "#6699ff", gemini: "#4d80f2" }; const logos: Partial> = { codex: "codex-logo.png", grok: "grok-logo.png", antigravity: "antigravity-logo.png", openCodeGo: "opencode-logo.png" }; -const n = (x: number) => new Intl.NumberFormat("en", { notation: x >= 10000 ? "compact" : "standard", maximumFractionDigits: 1 }).format(x); +const n = (x: number) => new Intl.NumberFormat("en", { notation: x >= 1000 ? "compact" : "standard", maximumFractionDigits: 1 }).format(x); const money = (x: number) => new Intl.NumberFormat("en", { style: "currency", currency: "USD", maximumFractionDigits: 2 }).format(x); const extract = (s?: Loaded) => s?.status === "value" ? s.value : undefined; const reason = (s?: Loaded) => s?.status === "missing" ? s.reason : "Not checked yet"; @@ -86,7 +86,7 @@ function TelemetryView({ provider, t, stats, chart, heatmap, daily, now, staticE if (!d) return ; const scope = !daily || mode === "daily" ? date(d.day) : mode === "weekly" ? `Week of ${date(shiftDay(d.day, -new Date(d.day).getDay()))}` : `Through ${date(d.day)}`; const label = `${scope}: ${measured ? `${exact.format(d.tokens)} tokens, ${exact.format(d.sessionCount)} sessions` : `${exact.format(d.sessionCount)} sessions, token totals unavailable`}`; - const readout = `${scope} · ${measured ? `${exact.format(d.tokens)} tokens · ${exact.format(d.sessionCount)} sessions` : `${exact.format(d.sessionCount)} sessions · token totals unavailable`}`; + const readout = `${scope} · ${measured ? `${n(d.tokens)} tokens · ${n(d.sessionCount)} sessions` : `${n(d.sessionCount)} sessions · token totals unavailable`}`; return ; })}}; } @@ -100,7 +100,7 @@ function AccountHeatmap({ reading, staticExport }: { reading: Loaded c.tokens ?? 0)); - return
c.tokens === undefined)}>{cells.map((c, i) => )}
; + return
c.tokens === undefined)}>{cells.map((c, i) => )}
; } export function ProviderCard({ slot, state, action, detail = false, staticExport = false }: { slot: Slot; state: ViewState; action: (r: Request) => Promise; detail?: boolean; staticExport?: boolean }) { const s = state.snapshot, key = slotKey(slot), fresh = extract(s.quotas[key]), q = fresh ?? s.archived[key], activity = extract(s.activities[key]), usage = extract(s.usages[key]), plan = extract(s.plans[key]) ?? q?.planType, accountActivity = s.accountActivities?.[key]; @@ -218,7 +218,7 @@ export function Settings({ state, action, saveOrder }: { state: ViewState; actio -

MeterUsage 0.2.41 · Cost figures are local estimates.

+

MeterUsage{state.appVersion ? ` ${state.appVersion}` : ""} · Cost figures are local estimates.

; } function AccountRow({ account: a, action }: { account: ViewState["settings"]["accounts"][number]; action: (r: Request) => Promise }) { diff --git a/src/shared/ipc.ts b/src/shared/ipc.ts index a594759..63dc508 100644 --- a/src/shared/ipc.ts +++ b/src/shared/ipc.ts @@ -14,6 +14,7 @@ export interface SettingsState { } export interface IssueReportState { sending: boolean; receipt?: string; error?: string; retryAt?: number } export interface ViewState { + appVersion?: string; issueReport?: IssueReportState; connections?: ConnectionState[]; snapshot: Snapshot; settings: SettingsState; systemDark: boolean; diff --git a/tests/renderer.test.ts b/tests/renderer.test.ts index 6b347e6..fdcc2e5 100644 --- a/tests/renderer.test.ts +++ b/tests/renderer.test.ts @@ -21,6 +21,14 @@ function renderApp(view: ViewState, surface = "flyout", error = "") { } const now = Date.parse("2026-10-06T12:00:00Z"); +it("shows the running bundle version in Settings without a stale fallback", () => { + const view = state(); + view.appVersion = "0.3.0-beta.1"; + expect(renderApp(view, "settings")).toContain("MeterUsage 0.3.0-beta.1"); + delete view.appVersion; + expect(renderApp(view, "settings")).toContain("MeterUsage · Cost figures"); + expect(renderApp(view, "settings")).not.toContain("MeterUsage 0.2.41"); +}); it("passes the saved size to notch details without scaling the strip or share surface", () => { const view = state(); for (const [size, scale] of [["small", "0.8"], ["medium", "1"], ["large", "1.15"]]) { @@ -281,7 +289,7 @@ it("shows rolling cost shares before token shares and handles an empty 30-day re usageWindows: [{ label: "last 24h", sessionCount: 1, messageCount: 2, tokens: tokens({ input: 200 }), estimatedCostUSD: 8 }, { label: "last 30d", sessionCount: 10, messageCount: 50, tokens: tokens({ input: 1000 }), estimatedCostUSD: 10 }] }; view.snapshot.usages.openCodeGo = value(usage); const cost = renderApp(view); expect(cost).toContain("80%"); expect(cost).toContain('aria-valuenow="80"'); expect(cost).toContain("Share of last 30d usage"); expect(cost).toContain("200 tokens · 2 messages"); - expect(cost).toContain("10 sessions · 50 messages · 1,000 tokens · ~$10.00"); + expect(cost).toContain("10 sessions · 50 messages · 1K tokens · ~$10.00"); usage.usageWindows[0].estimatedCostUSD = 20; expect(renderApp(view)).toContain('aria-valuenow="100"'); usage.usageWindows[1].estimatedCostUSD = 0; const token = renderApp(view); expect(token).toContain("20%"); expect(token).toContain('aria-valuenow="20"'); expect(token).toContain("Share of last 30d tokens"); @@ -425,7 +433,7 @@ it.each(["daily", "weekly", "cumulative"])("exposes exact heatmap values on hove const scope = mode === "weekly" ? `Week of ${date(shiftDay(day, -new Date(day).getDay()))}` : mode === "cumulative" ? `Through ${date(day)}` : date(day); expect(markup).toContain(`tabindex="-1" role="img" aria-label="${scope}: ${new Intl.NumberFormat().format(1234567)} tokens, 2 sessions"`); expect(markup).toContain(`title="${scope}: ${new Intl.NumberFormat().format(1234567)} tokens, 2 sessions"`); - expect(markup).toContain(`data-readout="${scope} · ${new Intl.NumberFormat().format(1234567)} tokens · 2 sessions"`); + expect(markup).toContain(`data-readout="${scope} · 1.2M tokens · 2 sessions"`); for (const name of ["daily", "weekly", "cumulative"]) expect(markup).toMatch(new RegExp(``)); const css = readFileSync("src/renderer/style.css", "utf8"); expect(markup).toContain('class="heatmap-readout"'); @@ -451,7 +459,7 @@ it("keeps OpenRouter ledger dates in UTC when the local date is a day earlier", const markup = renderToStaticMarkup(createElement(ProviderCard, { slot: primary("openRouter"), state: view, action: async () => {} })); const date = new Date(Date.UTC(2026, 9, 6)).toLocaleDateString([], { year: "numeric", month: "short", day: "numeric", timeZone: "UTC" }); expect(markup).toContain(`aria-label="${date}: ${new Intl.NumberFormat().format(1234567)} tokens, 2 sessions"`); - expect(markup).toContain(`data-readout="${date} · ${new Intl.NumberFormat().format(1234567)} tokens · 2 sessions"`); + expect(markup).toContain(`data-readout="${date} · 1.2M tokens · 2 sessions"`); } finally { vi.unstubAllEnvs(); } }); @@ -645,7 +653,7 @@ it("keeps unavailable account history explicit and respects global and Codex hea expect(css).toContain(".heatmap .unavailable-day > span"); }); -it.each([0, 42, 12500, 1234567, 499543210, 2400000000])("shows %s account tokens exactly in the visible readout", count => { +it.each([[0, "0"], [42, "42"], [999, "999"], [1000, "1K"], [1611, "1.6K"], [12500, "12.5K"], [1234567, "1.2M"], [499543210, "499.5M"], [2400000000, "2.4B"], [42413255594, "42.4B"]])("compacts %s account tokens to %s while preserving exact labels", (count, compact) => { vi.stubEnv("TZ", "America/Los_Angeles"); try { const view = state(); view.snapshot.activities = {}; @@ -655,14 +663,14 @@ it.each([0, 42, 12500, 1234567, 499543210, 2400000000])("shows %s account tokens for (const props of [{}, { detail: true }, { detail: true, staticExport: true }]) { const markup = renderToStaticMarkup(createElement(ProviderCard, { slot: primary("codex"), state: view, action: async () => {}, ...props })); const exact = `2026-10-11: ${new Intl.NumberFormat("en").format(count as number)} tokens`; - expect(markup).toContain(`aria-label="${exact}" title="${exact}" data-readout="${date} · ${new Intl.NumberFormat("en").format(count)} tokens"`); + expect(markup).toContain(`aria-label="${exact}" title="${exact}" data-readout="${date} · ${compact} tokens"`); expect(markup).toContain('aria-label="2026-10-10: Unavailable"'); } } finally { vi.unstubAllEnvs(); } }); it.each(providers)("uses the shared heatmap design for %s activity", provider => { - const view = state(), day = localDay(now), daily = [{ day, tokens: tokens({ output: 1234567 }), sessionCount: 2, estimatedCostUSD: 0 }]; + const view = state(), day = localDay(now), daily = [{ day, tokens: tokens({ output: 42413255594 }), sessionCount: 1611, estimatedCostUSD: 0 }]; view.snapshot.activities = { [provider]: value({ provider, scannedAt: now, sessions: [], daily, telemetry: telemetry([], daily, now) }) }; view.snapshot.appearance.heatmap = view.snapshot.appearance.codexHeatmap = view.snapshot.appearance.claudeHeatmap = true; const markup = renderToStaticMarkup(createElement(ProviderCard, { slot: primary(provider), state: view, action: async () => {} })); @@ -670,8 +678,8 @@ it.each(providers)("uses the shared heatmap design for %s activity", provider => expect(markup).toContain('class="heatmap-heading"'); expect(markup).toContain('class="heatmap-legend secondary"'); expect(markup).toContain('aria-label="Usage intensity from less to more"'); - expect(markup).toContain(`${new Intl.NumberFormat().format(1234567)} tokens, 2 sessions`); - expect(markup).toContain(`${new Intl.NumberFormat().format(1234567)} tokens · 2 sessions`); + expect(markup).toContain(`${new Intl.NumberFormat().format(42413255594)} tokens, ${new Intl.NumberFormat().format(1611)} sessions`); + expect(markup).toContain(`42.4B tokens · 1.6K sessions`); }); it("keeps every Settings category mounted and selects categories without dispatching an action", () => { @@ -754,7 +762,7 @@ it.each(["codex", "grok"] as const)("omits all %s heatmap Tab stops in static sh const markup = renderToStaticMarkup(createElement(ProviderCard, { slot: primary(provider), state: view, action: async () => {}, staticExport })); expect((markup.match(/tabindex="0"/g) ?? []).length).toBe(staticExport ? 0 : 1); if (staticExport) expect(markup).not.toContain("tabindex="); - expect(markup).toContain("12,345 sessions · token totals unavailable"); + expect(markup).toContain("12.3K sessions · token totals unavailable"); } });