Port Upstream Accuracy and Pricing Fixes - #1217
Conversation
Collapse provider details when the popover closes
* ci: move releases to NextByte infrastructure * fix: make first release retries recoverable * docs: finish release ownership migration
* Improve popover UI performance * Keep popover chrome fixed during navigation
* Improve spend legend name display * Show complete expanded legend names * Handle extreme legend content widths * Wrap only names wider than the legend * Account for legend spacing overflow
* Fix first-launch account discovery race * Bound subprocess pipe draining timeout * Clean up timed-out subprocess resources * Preserve subprocess group across fast exit * Restore detached descendant timeout cleanup * Clarify detached process ownership * Revalidate descendants before timeout signals * Make subprocess timeout cleanup ownership-safe * Validate subprocess descendant parentage * Remove unsafe test PID cleanup * Resume interrupted first-run seeding
* Include emails in multi-account provider names * Remove overview hover animation * Stabilize total spend legend hover * Preserve prices in legend hover * Scroll overlong legend labels * Clip legend marquee to row * Drop redundant unscoped account runtimes * Preserve ambient spend and reduce-motion labels * Disambiguate identityless account sources * Ignore inactive default account records * Preserve account aliases and saved order * Handle scoped-only account edge cases * Route bare peer history without a local runtime * Preserve unattributed synced spend * Validate Claude credential files before attribution * Validate account attribution and remote spend * Reject stale account cache for ambient Claude
* Improve multi-account menu bar stats * Preserve account pins across temporary absence
* Make panel height morphs a smooth in-window reveal The popover window now opens once per session at the screen-clamped maximum height and never resizes while open. The visible panel is a height-framed, corner-clipped SwiftUI card pinned to the window top; expanding a provider card (or switching screens) animates that frame on SwiftUI's clock while the AppKit backdrop and shadow follow the same values through a display-link-paced bridge. Per-frame NSWindow.setFrame calls — each a synchronous window-server commit — were what made the old growth stutter. Also: - Row reorder frames move to a reference box (ReorderFrameStore) so per-frame geometry churn during morphs no longer re-renders the whole list, and drag hit-testing reads live frames. - The height bridge drops duplicate pushes and the display link pauses itself when a morph goes quiet, so an idle popover costs no timer wakeups and doesn't pin ProMotion. - Outside-click dismissal hit-tests the visible panel rect instead of the (now taller) window frame. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Derive display-link rate from the anchored screen Hardcoding a 120 Hz ceiling made the backdrop pacing tick below the display cadence on higher-rate external displays. Also document the verified window-server click-through behavior for the fixed window's transparent region. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Let the backdrop follow spring overshoot past clamped targets Targets (and the opening guess) are already clamped before they animate, but the per-frame follower re-clamped interpolated values, so a spring undershooting a target that sits exactly on the 200pt minimum pinned the backdrop at the boundary while the SwiftUI panel dipped past it, splitting the two bottom edges for the tail of the animation. The follower now mirrors the raw rendered value; clamping stays at target selection. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Co-animate the panel height with the caret toggle The height retarget used to wait for the content measurement (~2 frames after the caret's row change), so the panel edge and the footer started moving on a second, phase-shifted spring — the footer visibly trailed the unfolding rows. The caret now retargets the height inside its own withAnimation via a remembered per-provider expanded-section delta (first toggle uses a row-count estimate; the measurement that follows corrects it and is remembered exactly), so rows, panel edge, and footer ride one spring clock from the first frame. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Remove leftover environment-key file The caret co-animation went through the MenuBarPopover seam instead; this environment key was unused. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Position the backdrop with direct frames, not Auto Layout The per-frame constraint-constant update for the backdrop's height ran the window's whole layout engine — the same engine holding the hosting view's edge pins — which re-entered the SwiftUI host every frame and knocked morph animation sampling off its vsync cadence (measured: 11-21ms/3-5ms alternating tick gaps, seen as bottom-edge and footer jitter). Setting the backdrop's frame directly dirties only its own subtree; ticks lock back to the display's 8.3ms cadence with even steps. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Apply backdrop heights synchronously with the SwiftUI frame Main-thread pushes from the height modifier now invoke the apply directly — a constraint-free frame set plus a shadow invalidation, nothing that re-enters layout — so the backdrop commits in the same transaction as the SwiftUI frame it matches. Any deferred apply (the display link, or a main-queue hop) landed 0-or-1 frames behind, and during a shrink a late backdrop poked out below the footer as a trailing tray strip that read as the footer lagging the bottom edge. The display link, its idle pause, and the bridge's paced mode are all deleted; the coalesced main-queue hop remains only as an off-main safety net, and the bridge tests now cover the synchronous contract. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Track the visual height while the panel is hidden Closing collapses every expanded card, but the collapsed re-measure lands after the panel is ordered out — and the visibility guard in the apply path skipped it while also poisoning the bridge's duplicate filter, so a reopen kept the previous session's expanded height (stale backdrop and remembered height). Hidden-time applies are now accepted: they're plain backdrop bookkeeping (the window never resizes), they keep the remembered height honest, and the reopen seed applies before the first paint. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Address code review: clip drag preview, supersede queued heights, docs - The reorder drag chip now renders inside the visual panel's rounded clip, so a drag past a short panel's bottom edge clips at the edge (as the old window bounds did) instead of floating into the fixed window's transparent remainder. - A synchronous main-thread height apply clears any height still queued by the off-main safety net, so a stale older value can never land after a newer one (regression test added). - The model-hover research doc no longer describes the removed dynamic-height window contract. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Decide each height push in a single critical section The duplicate check and the off-main queueing ran under two separate lock acquisitions, so a preempted off-main push could queue its older height after a newer main-thread push had already applied and cleared the slot. The whole decision — duplicate drop, sync-apply supersede, off-main scheduling — now happens under one lock, making that interleaving impossible by construction. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg * Key learned expansion heights by section composition A learned expanded-section delta survived Customize changes (moving a metric across the caret, hiding one, links changes), so the first toggle afterwards retargeted the panel by a stale amount and the measurement correction reintroduced a small footer catch-up. Deltas are now cached under a key derived from the provider's ordered On Demand metric IDs and quick-links presence — order included, since adjacent text rows condense — so a composition change misses the cache and falls back to the estimate + re-learn path instead of using a stale height. Claude-Session: https://claude.ai/code/session_01Crq3aP2viXSmWtCgtkFneg
…#21) * Compact footer: gear menu, short countdown, structurally pinned bar The footer collapses to one compact 40pt line: app version on the left, a short next-update countdown (like "5m", click or Cmd-R to refresh, mini spinner while updating) and a gear menu button on the right replacing the "Options" capsule. The bar is now fixed-height chrome rendered as a bottom-aligned overlay on the animated panel frame, so it stays glued to the panel's bottom edge through every height morph by construction; a clear spacer in the scroll view's safe area keeps the content inset and the native bottom scroll-edge blur. PanelHeightCoordinator takes the footer height as an init constant (like the top bar) instead of measuring it. Claude-Session: https://claude.ai/code/session_01SWYFDiqdQ8gRGJTunKjcPP * Hold the footer countdown perfectly still The per-second tick rolled its digits (numericText) and narrowed the label as digits dropped, nudging the refresh glyph sideways — the one thing left moving in the pinned footer. The text now swaps in place with no transition, in a slot reserved for the widest value ("59s"). Claude-Session: https://claude.ai/code/session_01SWYFDiqdQ8gRGJTunKjcPP * Keep the footer riding the panel edge through two-phase morphs Rendering the footer as a bottom-aligned overlay on the animated height frame made its position animate as its own attribute: when a content change landed in one transaction and the height retargeted in a second (a provider appearing, a caret estimate correction), the overlay's spring ran phase-shifted from the frame growth and the footer visibly trailed the moving edge, sliding over content to catch up. The footer now lives back in the safe-area bar, whose position is re-derived from the per-frame layout of the animated frame — verified frame-by-frame to hug the bottom edge through both co-animated and measurement-driven morphs. The compact fixed-height design is unchanged.
Includes the release-time pricing snapshot refresh.
The refresh countdown brightens from secondary to primary on hover, and the gear gets a subtle fill inside its glass circle. Both reset when the popover closes or the control unmounts, so a screen switch or reopen can't strand a stale highlight. Claude-Session: https://claude.ai/code/session_01Lfvn6KcURg32FgptPoHcWd
…le error card (#24) * Suppress Keychain UI on automatic refreshes and add an empty-state error card Background and launch refreshes read classic login-keychain items (Claude Code credentials, Chromium Safe Storage keys) guarded only by LAContext.interactionNotAllowed, which the classic ACL confirmation dialog ignores - so macOS could pop password prompts during automatic refreshes and block them behind the dialog. A process-wide KeychainUISuppression gate built on SecKeychainSetUserInteractionAllowed now keeps every non-interactive Security.framework operation prompt-free (a protected item fails fast into the friendly Permission Required state), while interactive reads from a manual Refresh hold the gate so a racing background read cannot disable UI beneath an open approval dialog. Providers whose refresh failed with no displayable data now replace their empty "No data" rows with an actionable error card (reason plus a Refresh button - the one action allowed to show a Keychain prompt), keeping quick links behind the caret, mirrored in the drag preview and share export. Claude-Session: https://claude.ai/code/session_01Ur7PxLJ1avetsk3JegAWVY * Fail non-interactive keychain operations when UI suppression cannot engage SecKeychainSetUserInteractionAllowed's return status was ignored: had the disable call ever failed, the suppression scope would have run its protected Security.framework call with interaction still enabled - able to show the exact background dialog the gate exists to prevent. The scope now reports whether suppression actually engaged; when it did not (logged loudly), non-interactive bodies skip the prompt-capable call and report their unavailable outcome instead, and a failed restore is retried before the next interactive operation so approval prompts cannot stay broken. Claude-Session: https://claude.ai/code/session_01Ur7PxLJ1avetsk3JegAWVY
…ames (#25) The refresh-error triangle now sits at the header's right edge in the copy control's slot, the copy button overlays a reserved gutter so its hover reveal never reflows the title, long provider/account names marquee to their ending on hover (shared with the Total Spend legend's machinery, now extracted to HoverMarqueeText), and the Outdated tag keeps its full width instead of rendering glyph slivers. Claude-Session: https://claude.ai/code/session_017bkz3Au6LY2mPBquPJWFYT
The fork is charting its own direction; the remaining unshipped phases (Cowork-as-card, cswap) are not planned work here. Shipped behavior from the executed phases is documented in docs/providers/claude.md and docs/dashboard.md. Claude-Session: https://claude.ai/code/session_01Mg9MQuFQjWPwW7vpaAC8Vc
## TL;DR Records the approved v0.8.9 release notes before the release tag is created. ## What was happening - The two changes merged after v0.8.8 were not yet represented in the changelog. ## What this changes - Adds the approved v0.8.9 Bug Fixes section. - Adds PR, author, commit, and full-comparison links for the release range. ## Tests - `swift test` — 1,736 XCTest cases passed, 4 skipped; 6 Swift Testing cases passed. - `git diff --check`
**TL;DR** — Move the TestFlight jobs into a dedicated reusable workflow and make that workflow, rather than the shared Mac release workflow, the iOS gate boundary. Unrelated Mac release and update-feed edits no longer cause TestFlight uploads. **What was happening** - The gate treated any change to `.github/workflows/release.yml` as iOS-relevant. - That file also owns the Mac release, appcast, website, and update-feed work, so unrelated edits such as the v0.8.7 landing-page change caused a fresh TestFlight archive, upload, and Beta App Review submission. - The gate did correctly skip v0.8.8, showing that the baseline logic worked; the workflow path was simply too broad. **What this changes** - Extracts the existing iOS Gate, iOS TestFlight, and TestFlight External jobs into `.github/workflows/release-ios.yml`, called in parallel with the Mac release. - Tracks that dedicated workflow and the TestFlight scripts as iOS-relevant while excluding the general `release.yml`. - Moves path classification into a small tested module and handles moves out of `ios/` as relevant deletions. - Adds the Node regression tests to CI and updates the release documentation. **Heads-up** - The first release after this merges intentionally ships TestFlight once because the TestFlight workflow itself changed. Later Mac-only `release.yml` edits skip it. - Genuine iOS packaging changes still ship: replaying history classifies v0.8.9 as relevant because `script/release_ios.sh` changed. **Tests** - `swift test` — 1,736 passed, 4 skipped, 0 failed. - `node --test script/lib/testflight_paths.test.mjs` — 3 passed. - `actionlint` v1.7.12 on `ci.yml`, `release.yml`, and `release-ios.yml`. - Historical classifier replay: v0.8.7 skip, v0.8.8 skip, v0.8.9 ship. - YAML parse and `git diff --check` pass.
## TL;DR Remove Runway's deprecated, process-global Keychain UI suppression switch. Automatic credential discovery is limited to noninteractive metadata and local caches, while any potentially prompting Keychain secret read happens only after an explicit user action. ## What was happening - Runway called `SecKeychainSetUserInteractionAllowed(false)` around synchronous `SecItemCopyMatching` reads, then restored the setting after the read returned. - Because the switch is process-global and the read can block indefinitely, a wedged call could keep interaction disabled longer than intended. - Startup, periodic refresh, CLI refresh, and provider detection could reach credential paths that read classic login-keychain secret data. - A macOS 26.6 probe confirmed that `LAContext.interactionNotAllowed` does not reliably suppress classic ACL prompts when secret data is requested. ## What this changes - Delete the deprecated global interaction switch and its suppression wrapper. - Split Keychain access into metadata/fingerprint probes and secret-data reads. - Attach a per-query `LAContext` with `interactionNotAllowed` to automatic metadata-only queries. - Restrict secret reads to explicit GUI refresh/recovery actions and serialize them. - Reuse a user-approved value for the running app session while the item's metadata fingerprint remains unchanged, so scheduled refreshes continue without another prompt or secret read. - Stabilize newly modified Keychain metadata before the approved read, avoiding stale process-lifetime caching when secret-only updates share Keychain's one-second modification timestamp. - Keep breaker retries, same-refresh deduplication, and no-fingerprint fallback behavior bounded. - Move hidden Codex keyring-home identity binding to manual Refresh All instead of launch, CLI, or periodic refresh paths. - Store Runway's iCloud device identity in a protected local Application Support file, with one-time migration and explicit recovery for legacy Keychain identities. - Update provider errors, settings UI, architecture/debugging/provider docs, and regression coverage for the new behavior. ## Heads-up - The modern noninteractive context is intentionally not treated as a reliable suppression mechanism for classic login-keychain secret ACL reads; those reads remain manual. - Existing legacy iCloud identities migrate automatically when possible. If migration needs Keychain approval, Settings offers an explicit Recover Identity action. ## Tests - `swift test`: 1,745 XCTest cases passed, 4 skipped, 0 failures; 6 Swift Testing cases passed. - Focused Keychain coordinator/accessor suite: 24 passed, 0 failures. - Real macOS 26.6 probe confirmed Keychain modification dates collide within the same second; regression coverage verifies the stabilization barrier. - `./script/build_and_run.sh verify`: signed release build verified and relaunched. - Local Codex review loop: clean after the lifetime and collision fixes. - `git diff --check`: clean. - Confirmed `SecKeychainSetUserInteractionAllowed` is absent from source and the built Mach-O imports.
…99) **TL;DR** — Ends the recurring keychain prompts for good: one Always Allow per login, ever. Approved credentials now load silently on every launch and refresh (no per-session Connect click), survive other apps resetting the item's sharing rules, and expired Claude tokens renew themselves under strict single-chain guards. ## What was happening - Ad-hoc-signed dev builds (bare `swift build` output) could raise approval dialogs whose Always Allow died with the next rebuild — the source of the "prompts on every rebuild" storms, and of dead cdhash entries accumulating in item ACLs. - Every app relaunch parked keychain-backed providers on the Connect state until a manual refresh, because automatic paths were metadata-only: `LAContext.interactionNotAllowed` provably fails to suppress classic ACL dialogs, so no background secret read was safe. - A recorded permission denial silently softened to the neutral Connect state after the 15-minute breaker revalidation, and a cancelled read erased it entirely. - Credential writers reset their keychain item's partition list on rotation (observed live with Claude Code), stranding Runway on `errSecAuthFailed` with intact ACL approvals — recoverable only by typing the keychain password. - Expired Claude logins (e.g. an idle second profile) flatlined to "login needs renewal" until the user manually used that profile, because Runway was strictly read-only after the refresh-token-reuse incidents (OpenAI robinebers#516; CodexBar's Claude desync robinebers#1161). ## What this changes - `InteractiveKeychainReadGate` refuses approval dialogs from ad-hoc-signed builds (neutral Connect state + loud log naming the fix) — an approval that can't outlive a rebuild is never requested. - Background reads run one secret read inside a "quiet turn" with the process-global UI switch off (`SecKeychainSetUserInteractionAllowed(false)` — deprecated but still Apple's documented answer for foreign-item reads, and empirically the only mechanism that suppresses classic ACL dialogs). Approved logins load silently; a would-prompt read falls to the neutral deferral. Dialog-capable reads bail out behind a wedged quiet holder instead of hanging. - Denials are durable: they survive unattended revalidation of an unchanged item and cancelled reads store no evidence; only a successful interactive read or a proven item change clears them. - `PartitionWallFallbackReader` recovers from partition-list resets by reading through `/usr/bin/security` — only after proving from the item's own ACL metadata that the helper is silently authorized, so it can never prompt. - `ClaudeTokenRenewal` renews an expired Claude token reactively (expired ≥10 min, past Claude Code's own proactive-renewal horizon), verifies a write path before consuming the refresh token, adopts a concurrent writer's fresher chain instead of racing it, and writes the rotated blob back to the exact store it came from — through the security helper only, with the secret over stdin. `invalid_grant` is terminal with backoff. Kill switch: `runway.claude.disableTokenRefresh`. - Foreign keychain items are never written in-process: securityd rewrites the partition list to the writer's partition on every update, which locks the owning app's own tooling out (learned live, both directions). ## Heads-up - Two audited exemptions to the no-`security`-CLI rule (enforced by the reshaped containment tests): the partition-wall fallback reader and the renewal write-back, both gated on proven-silent authorization. - The `security` CLI usage and the UI-switch call are each confined to one file; `SecurityCLIUsageTests` fails on any new call site. - Claude Desktop and environment credentials remain strictly read-only; the legacy service-wide item is never written (unknown account). - Docs updated: `refreshing.md`, `dashboard.md`, `debugging.md`, `providers/claude.md`. ## Tests Full suite passes (unit coverage for the gate, quiet reads, denial persistence, partition fallback proof-before-launch, renewal guards/CAS/write-back). Verified live on a real machine: cold launch loads all 8 providers with zero clicks and zero dialogs; a partition-reset item recovered silently; a day-expired second-profile token renewed and wrote back on the first eligible cycle. https://claude.ai/code/session_01RbrQY7hdj5JeZdk5cNiMcY
**TL;DR** — Records the v0.8.10 changelog ahead of tagging the release. ## What was happening - The v0.8.10 release is ready to cut (keychain overhaul #99, plus #98 and #97), and the repo records each release's notes in CHANGELOG.md via PR before the tag. ## What this changes - Prepends the approved v0.8.10 section to CHANGELOG.md. No code changes. ## Heads-up - The v0.8.10 tag goes on this PR's squash-merge commit; the release workflow then builds, signs, notarizes, and publishes. https://claude.ai/code/session_01RbrQY7hdj5JeZdk5cNiMcY
**TL;DR** — The Key Limit meter now charges the current limit window's spend against the key's cap, instead of the key's lifetime spend. Port of upstream openusage robinebers#1109. **What was happening** - The `/key` endpoint reports `usage` (lifetime spend on the key) and `limit_remaining` (what is left in the configured limit window). - The Key Limit meter used lifetime `usage` against the cap, so once a key had ever spent more than one window's cap, the meter pinned at or past 100% even right after a daily/weekly/monthly reset. **What this changes** - `OpenRouterUsageMapper.keyMetrics` computes used as `max(0, limit − limit_remaining)`, so the meter tracks the current window. - Regression test: lifetime `usage` of 12 with `limit` 5 / `limit_remaining` 3 now maps to 2 used of 5. - `docs/providers/openrouter.md` describes the window semantics. **Heads-up** - A key whose payload has `limit` but no `limit_remaining` now shows as fully used rather than at lifetime spend; this matches upstream's tested behavior, and OpenRouter always returns `limit_remaining` alongside a configured `limit`. **Tests** - `swift test --filter OpenRouter` — 30 tests, 0 failures.
**TL;DR** — Z.ai renamed its percentage quota type from `TOKENS_LIMIT` to `CREDIT_LIMIT`; the mapper now accepts both, so current plans get their Session and Weekly meters back. Port of upstream openusage robinebers#1105. **What was happening** - The quota mapper only matched `limits` entries typed `TOKENS_LIMIT`. - Current Z.ai responses (e.g. GLM Coding Lite, captured 2026-08-13) type the same percentage windows as `CREDIT_LIMIT`, so users on current plans saw no Session/Weekly meters at all. **What this changes** - `ZAIUsageMapper` filters percentage quota entries by `CREDIT_LIMIT` or legacy `TOKENS_LIMIT` (checking both the `type` and `name` fields, as before). - Adds a live-capture regression test mapping a `CREDIT_LIMIT` response to the Session and Weekly meters, including an entry with no `nextResetTime` on the active window. - `docs/providers/zai.md` describes the renamed quota type. **Tests** - `swift test --filter ZAI` — all mapper, provider, and live-response suites pass, including the new `testMapsCurrentCreditLimitResponseToSessionAndWeekly`.
**TL;DR** — Routine dependency bump of Sparkle to 2.9.5, matching upstream openusage robinebers#1082. **What was happening** - `Package.resolved` pinned Sparkle 2.9.4; 2.9.5 is out (bug-fix release). **What this changes** - `swift package update Sparkle` — the pin moves to 2.9.5. `Package.swift` already allows it (`from: "2.9.4"`). The `originHash` was recomputed by the toolchain as a side effect. **Tests** - `swift build` succeeds against the new pin.
**TL;DR** — The pricing store now compares `updated_at` and serves whichever of the on-disk cached supplement and the bundled supplement is newer, instead of always preferring the cache. Port of upstream openusage robinebers#1089. **What was happening** - `ModelPricingStore.loadSupplement` served the cached supplement whenever one decoded, falling back to the bundled file only when the cache was missing or unreadable. - After an app update that ships a newer bundled supplement, the stale cache from the previous version still won — for up to an hour until the feed refetch, and forever for anyone who can't reach the feed. **What this changes** - `loadSupplement` decodes both copies and picks the newer by `updated_at` (lexicographic on the zero-padded ISO date). Bundled wins only when strictly newer, so the usual feed-ahead-of-shipped case keeps serving the cache. - A missing `updated_at` counts as oldest — an undated file never displaces a dated one. - Three regression tests cover newer-bundled-beats-older-cache, newer-cache-beats-older-bundled, and the undated cases; `docs/pricing.md` documents the behavior. **Tests** - `swift test --filter Pricing` — 56 tests, 0 failures (includes the 3 new ModelPricingStore tests).
**TL;DR** — Syncs the pricing supplement with Cursor's current published pricing and the alias fixes upstream openusage shipped in robinebers#1101/robinebers#1103/robinebers#1093/robinebers#1087: prices Grok 4.6, corrects Grok 4.5 Fast output, and adds Kimi K3, Daybreak Blue, dashed-slug, bare-composer, and Cursor Router label aliases. **What was happening** - The supplement was last synced 2026-08-02. Since then Cursor shipped Grok 4.6, Kimi K3, and Router rows, and corrected Grok 4.5 Fast — so those models showed the unpriced-model warning triangle or priced wrong. **What this changes** (source: [Cursor models & pricing](https://cursor.com/docs/models-and-pricing.md), cross-checked against upstream openusage robinebers#1101/robinebers#1103/robinebers#1093/robinebers#1087) - **`grok-4.6`**: $2 input / $2 cache write (not published separately, defaults to input) / $0.50 cache read / $6 output. **`grok-4.6-fast`**: $4 / $4 / $1 / $12. Cursor lists a 50% one-week launch discount from 2026-08-12; these are the table rates as of 2026-08-13. - **`grok-4.5-fast` output corrected $18 → $12** to match the current table. - **Grok slug aliases** for 4.5/4.6 accept dashed versions (`grok-4-6-xhigh`) via `[.-]`, matching real Cursor CSV slugs (upstream robinebers#1103). - **Kimi K3**: no supplement entry — models.dev's `moonshot/kimi-k3` already matches Cursor's table ($3 / $0.3 cache read / $15), so the existing alias is broadened to cover `-code` and effort suffixes. This deliberately diverges from upstream robinebers#1087, which duplicated the rates because its catalogs lacked the model. - **`daybreak-blue-latest`** (and `gpt-` prefixed form) aliases to `gpt-5.6-sol`, per OpenAI's Daybreak docs (upstream robinebers#1093). - **Bare `composer`** slug aliases to `composer-2.5` (upstream robinebers#1087). - **Cursor Router prose labels** ("Opus 5 (Auto Balanced)", "Kimi K3 (Auto Cost)", …) get case-insensitive alias rules to their routed models — 20 rules covering the Claude, Composer, Grok, GPT, Gemini, GLM, and Kimi rows (upstream robinebers#1087). - `updated_at` bumped to 2026-08-13. **Heads-up** - On merge the supplement publishes to `update-feed`, so installed apps pick this up within about an hour — no release needed. I'll verify the publish workflow after merge. **Tests** - CI's supplement validator passes locally. - `swift test --filter "ModelPricing|PricingBundledResource"` — 57 tests, 0 failures, including new suites for Grok 4.6, Kimi K3, bare composer, Daybreak Blue, and all 20 Router labels.
**TL;DR** — The OpenCode Session / Weekly / Monthly meters now come from OpenCode's official usage endpoint (`GET https://opencode.ai/zen/go/v1/usage`, authorized with the local `opencode-go` key) instead of being reconstructed from local SQLite logs with hardcoded dollar caps and window math. Port of upstream openusage robinebers#1097. **What was happening** - The Go plan meters were estimated locally: `OpenCodeGoWindows` reimplemented the rolling 5-hour session, ISO-week, and anchored-month windows over this machine's logs, bounded by hardcoded dollar caps. - That undercounted account usage from other machines, drifted from OpenCode's own accounting, and broke whenever OpenCode changed plan caps. **What this changes** - New `OpenCodeUsageClient` calls the official usage API; the mapper turns `{ usage: { rolling, weekly, monthly } }` into three percent meters with reset countdowns — the same numbers the OpenCode dashboard shows, account-wide. - The provider distinguishes a rejected key (401), a valid key with no Go subscription (403 `EntitlementError` → meters hidden, spend tiles stay), and transport errors. If the local database is unreadable but the API works, the meters still render and the failure is logged. - `OpenCodeGoWindows` (147 lines of window math) and its tests are deleted; the scanner now only feeds the spend tiles and trend from local logs. - Metric IDs (`opencode.session/weekly/monthly`) are unchanged, so layout customizations, pins, and defaults carry over; the widgets switch from bounded-dollar meters to percent meters. Docs updated (`docs/providers/opencode.md`, `docs/local-http-api.md`). **Heads-up** - Runway-specific adaptations from upstream: no telemetry `ErrorCategory` (Runway removed PostHog), and the scanner keeps Runway's `DayKeyCache` day-key memoization. - The local HTTP API's `session`/`weekly`/`monthly` units change from `usd` (estimated) to `percent` — anything scripted against those units will see the new unit field. **Tests** - `swift test` — full suite, 1772 tests, 0 failures (42 OpenCode tests, ported from upstream with the Runway adaptations above).
**TL;DR** — Org-managed Copilot Business/Enterprise seats now show the user's own `credits_used` as a personal Credits count, and the org-billing lookup appends to those lines instead of replacing them. Re-implementation of upstream openusage robinebers#1108 (issue robinebers#1094) on Runway's diverged Copilot provider. **What was happening** - GitHub's token-based-billing placeholder response has a zero-entitlement premium bucket, so the mapper returned no personal lines at all — but the same bucket can carry a real `credits_used` count (the user's own per-seat consumption, upstream issue robinebers#1094). - The provider then *replaced* the mapped lines with the org-billing outcome, so even if the mapper had produced a personal line, an org lookup (or the managed-account badge) would have wiped it. - A plain org member (no billing access) ended up with just the managed-account badge and no usage at all, despite GitHub reporting their own consumption. **What this changes** - The mapper emits a personal **Credits** count (unbounded — there's no allotment to divide by) on org-managed seats when `credits_used` is a real positive number; zero or missing stays "No data", and the placeholder's `overage_permitted` still never produces an Extra Usage row (issue robinebers#839 stays fixed). - The provider appends org-billing lines (Org Credits / Org Spend, or the managed-account badge) to the mapped lines instead of overwriting them, so members see their own Credits beside the badge and admins see both personal and org-wide numbers. - `applicableMetricIDs` on org-managed seats now derives from the lines present (including `copilot.premium` for the personal count) instead of hard-coding the badge-only set. - The `premiumCredits` export declares `unit: "credits"` with a `.progressOrValue(kind: .count)` source, so the local HTTP API serves the paid-plan percent meter and the org-seat personal count from the same resource. - Docs updated (`docs/providers/copilot.md`, `docs/local-http-api.md`). **Heads-up** - Runway divergence from upstream: Runway keeps its managed-account badge and keychain-error handling in the org path — a keychain problem still fails the refresh loudly rather than silently showing partial data; upstream has no badge state. - No new metric IDs; layout/pin defaults are untouched (the personal count renders through the existing `copilot.premium` widget). **Tests** - `swift test` — full suite, 1776 tests, 0 failures. New coverage: mapper emits/suppresses the personal count, member-with-403 keeps Credits + badge, admin keeps Credits + Org Credits/Org Spend, and the local API exports count vs percent correctly.
**TL;DR** — Prices Gemini 3.7 Flash (Cursor's CSV now emits `gemini-3.7-flash-high`, which showed the unpriced-model warning), broadens the Gemini alias rules to accept effort suffixes, and fixes the Codex long-context (>272k) rate tier for GPT-5.6 Terra/Luna, which still used the pre-reprice base rates. Selective port of upstream openusage robinebers#1112. **What was happening** - `gemini-3.7-flash` was in no pricing source and had no alias rule, so its spend showed the warning triangle and was excluded from the tiles. - None of the Gemini alias rules accepted Cursor's effort suffixes (`-low/-medium/-high/-xhigh`), so slugs like `gemini-3.6-flash-high` didn't resolve either. - The Codex long-context tier for GPT-5.6 Terra/Luna was derived from the old base rates that were repriced away on 2026-07-30 — long-context Terra spend was overestimated ~25% and Luna ~5×. **What this changes** - Adds a `gemini-3.7-flash` supplement entry: $0.75 input / $0.75 cache write (unpublished, defaults to input) / $0.075 cache read / $3.50 output, per Cursor's published table (the repo rule: the Cursor page is the source of truth for CSV pricing). Google and LiteLLM publish $3.75; upstream robinebers#1112 went with that instead — the entry's comment records the discrepancy for easy revisiting. - Broadens every Gemini alias rule with `(?:-(?:low|medium|high|xhigh))?`, adds the 3.7 rule, and merges the two 3.1 Pro rules into one. - Adds Cursor Router rows for "Gemini 3.6 Flash (Auto…)" and "Gemini 3.7 Flash (Auto…)". - Corrects `CodexLogUsageAggregation` long-context rates: Terra `(5, 22.5, 0.5)` → `(4, 18, 0.4)`, Luna `(2, 9, 0.2)` → `(0.4, 1.8, 0.04)` — consistent with the current base rates (2× input, 1.5× output, 2× cache read). - `updated_at` → 2026-08-16. **Heads-up** - Deliberately not ported from robinebers#1112: the GPT-5.6 Terra/Luna supplement rates and 2.0 fast multipliers (Runway already repriced these on 2026-07-30 — upstream caught up to us), and a `gemini-3.6-flash` supplement entry (Runway's bundled LiteLLM snapshot already carries it at the same $1.50 rate, so the alias-to-catalog rule stands). - On merge, the supplement publishes OTA via `update-feed`, clearing the Gemini 3.7 warning for installed apps within about an hour. The Codex tier fix ships with the next release. **Tests** - CI's supplement validator passes locally. - `swift test` — full suite, 1791 tests, 0 failures. New assertions: `gemini-3.6-flash-high` and `gemini-3.7-flash-high` resolution (input and output rates), the two new Router rows, and the corrected Terra/Luna long-context costs.
**TL;DR** — Records the v0.8.11 changelog ahead of tagging the release. **What was happening** - Eight PRs (#101–#108) merged since v0.8.10 — the upstream openusage port batch plus the Gemini 3.7 / Codex long-context pricing fix — with no changelog entry yet. **What this changes** - Prepends the owner-approved v0.8.11 section to `CHANGELOG.md`: seven entries under Bug Fixes, the Sparkle 2.9.5 bump under Chores, and the full commit list for the v0.8.10...v0.8.11 range. **Heads-up** - Once this merges, `v0.8.11` is tagged on the merge commit to start the release run; the same notes are published onto the GitHub Release.
## TL;DR Restore Grok CLI spend and token estimates for Grok 1.x by reading its persisted per-session usage ledger while preserving the legacy unified-log fallback. ## What was happening - Runway only scanned the older Grok unified log for inference token rows. - Grok 1.x persists completed-turn usage under its sessions directory instead, so active users could see No data in Today, Yesterday, and Last 30 Days. - Current Grok CLI model IDs such as grok-4.5-build and grok-4.6-build did not resolve to the shared pricing catalog. ## What this changes - Reads measured per-model token buckets from completed Grok 1.x turns with an incremental persisted JSONL cache. - Excludes nested subagent sessions already represented by their parent turn and deduplicates replayed fork history by prompt and model. - Prefers modern session data on overlapping days while retaining legacy-only history. - Adds pricing aliases for the current Grok Build model IDs. - Keeps cancelled scans from publishing the legacy fallback as authoritative. - Updates Grok provider documentation and adds parser, integration, overlap, pricing, and cancellation regressions. ## Heads-up - Dollar values remain estimates at public API rates and are separate from Grok's weekly subscription utilization. - A plain full-suite run encountered an existing hang in the tampered provisioning-profile OpenSSL test. The broad suite below excludes only that test. ## Tests - swift test --filter Grok - swift test --filter JSONLScannerCancellationTests - swift test --skip ProvisioningProfileScriptTests.testProfileDecoderRejectsATamperedCMSPayload - 1,794 XCTest tests plus 6 Swift Testing tests passed - 4 normal skips, 0 failures - Signed release build, relaunch, process check, and live local API verification against real Grok 1.x session data
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## TL;DR Selective re-implementation of the new upstream OpenUsage commits that still apply on Runway: Codex auto-review stays a first-class model in spend breakdowns and prices as GPT-5.6 Luna from 2026-07-09, and Grok CLI's `grok-proxy` slug resolves to Grok Build. ## What was happening - Upstream has moved on since the last port wave (Runway #101–#108). Each new OpenUsage commit was reviewed against Runway's architecture, existing ports, and the "don't bloat" bar. - Codex still replaced `codex-auto-review` with its dated GPT fallback before aggregation (openusage robinebers#1085), so the breakdown showed gpt-5.5 instead of auto-review. - That fallback table still stopped at gpt-5.5 (openusage robinebers#1125). OpenAI moved auto-review onto GPT-5.6 Luna on 2026-07-09, so every auto-review event since July was priced 25× too high. - Grok CLI now logs `grok-proxy` for Grok Build (openusage robinebers#1123); that slug had no alias, so those tokens were dropped from the spend tiles. ## What this changes - Parser keeps the measured `codex-auto-review` slug and stores the dated GPT fallback on `Event.pricingModel` only for cost. Aggregation keys the rate cache by both slugs so a day that spans the Luna cutoff does not reuse the first event's rates. - Adds `("2026-07-09", "gpt-5.6-luna")` at the top of the auto-review fallback table (ours; ccusage's snapshot still stops at gpt-5.5). Bumps the Codex JSONL cache schema to 2 so already-remapped events are reparsed. - Aliases `grok-proxy` → `grok-build-0.1` and stamps `updated_at` with a same-day ISO timestamp so the feed wins over today's date-only cache. - Documents the auto-review identity/pricing split on the Codex spend-tiles page. ## Heads-up Reviewed and **not** ported, with reasons: - **openusage robinebers#1116 / robinebers#1127** (mandatory daily analytics ping, PostHog bump) — Runway removed analytics in #9. - **openusage robinebers#1111 / robinebers#1106** (scroll / reorder / SVG parse) — Runway already has `ReorderFrameStore`, parsed-once `ProviderMark`, row-local reorder gestures, and the rebuilt popover scroll path. Taking their patch would duplicate and fight that work. - **openusage robinebers#1033** (Reset All Settings) — Runway already has Reset All Customization; a second Settings-wide reset is extra surface, and earlier port waves already skipped it. - **openusage robinebers#1128** (Sparkle 2.9.5 → 2.9.6) — relevant security bump, but this environment cannot refresh `Package.resolved`'s `originHash`. Dependabot is already scheduled weekly; worth a dedicated follow-up. - Pricing/provider commits through robinebers#1112, robinebers#1108, robinebers#1110, robinebers#1109, robinebers#1105, robinebers#1103, robinebers#1101, robinebers#1097, robinebers#1093, robinebers#1089, robinebers#1087, robinebers#1050, robinebers#1019, robinebers#1082 are already in `main`. ## Tests - New parser tests: auto-review lines keep the slug; post-2026-07-09 lines price as Luna; the 2026-07-08/09 boundary stays gpt-5.5. - New aggregation tests: breakdown shows `codex-auto-review` while costing the fallback; Luna and gpt-5.5 auto-review events on either side of the cutoff keep independent rates (guards the rate-cache key). - `grok-proxy` resolves to the same rates as `grok-build-0.1`. - This environment is Linux, so `swift test` could not be run here. CI on `macos-26` is the compile/test gate. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-28a06551-9894-4f4b-ac34-9d5daf6e62b1?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-28a06551-9894-4f4b-ac34-9d5daf6e62b1&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Re-implement the OpenUsage commits that fix real usage gaps without adding new metrics or fighting Runway's architecture: Desktop account-prefixed caches, Claude spend without OAuth, Grok subagent ledgers, Codex Business Premium, Cursor Models/Other Models labels, and new model rates.
|
Thanks for your interest in contributing to OpenUsage! External pull requests must reference an open issue that:
Please discuss the change on an issue first, wait for a maintainer to approve and assign it to you, then reopen this pull request with |
|
Opened against the wrong repository by mistake — this was meant for the Runway fork (mstallone/runway), not OpenUsage. Sorry for the noise. |
There was a problem hiding this comment.
🟡 Changes recommended
script/classify_appcast_bootstrap.sh can fail under set -u when stdin is empty, which breaks the “no releases yet” path it is meant to support.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Ports a large set of upstream accuracy/pricing fixes into the Runway fork while continuing the OpenUsage → Runway rebrand, expanding shared provider/pricing infrastructure, and adding iOS companion app scaffolding + CI compile coverage.
Changes:
- Rebrands user-facing copy/identifiers (Runway naming, bundle IDs, defaults keys, CLI/env vars) and updates policy/docs/templates accordingly.
- Introduces/refactors shared infrastructure for pricing, history/snapshots, limits exports, UI surfaces (share cards, pills/popovers), and provider clients/mappers.
- Adds iOS app + widgets project assets/config and extends CI to compile-check iOS plus gate TestFlight-relevant paths.
File summaries
| File | Description |
|---|---|
| TRADEMARK.md | Rebrand trademark policy to Runway fork. |
| SECURITY.md | Update vuln reporting links/copy. |
| CODE_OF_CONDUCT.md | Update enforcement contact to repo owner. |
| .gitignore | Ignore additional iOS/Xcode artifacts. |
| Package.resolved | Dependency resolution update (Sparkle, PostHog pin removed). |
| .github/workflows/ci.yml | Pin checkout SHA; add iOS build + node tests. |
| .github/workflows/stale.yml | Remove stale bot workflow. |
| .github/workflows/labeler.yml | Remove PR labeler workflow. |
| .github/workflows/deploy-pages.yml | Remove Pages deploy workflow. |
| .github/PULL_REQUEST_TEMPLATE.md | Update PR policy text; remove screenshots section. |
| .github/labeler.yml | Remove labeler configuration. |
| .github/ISSUE_TEMPLATE/new_provider.yml | Rebrand issue template text. |
| .github/ISSUE_TEMPLATE/feature_request.yml | Rebrand issue template text. |
| .github/ISSUE_TEMPLATE/config.yml | Allow blank issues. |
| .github/ISSUE_TEMPLATE/bug_report.yml | Rebrand bug report labels/help text. |
| .github/CODEOWNERS | Switch default reviewers to fork owner. |
| .cursor/rules/runway-preview-launch.mdc | Rebrand local dev launch workflow guidance. |
| .codex/environments/environment.toml | Rebrand Codex environment metadata. |
| .agents/skills/signing-entitlements/SKILL.md | Update codesigning identity guidance. |
| .agents/skills/macos-signing-entitlements/SKILL.md | Update macOS codesigning identity guidance. |
| .agents/README.md | Document additional skills layout/notes. |
| docs/proxy.md | Rebrand proxy docs + refresh cadence wording. |
| docs/providers/devin.md | Rebrand provider doc copy. |
| script/update_pricing_snapshots.sh | Update resource paths to Runway. |
| script/validate_release_tag.sh | Add stable-release tag validator. |
| script/stamp_website_version.sh | Stamp website mock footer version. |
| script/normalize_appcast_title.sh | Normalize appcast RSS channel title. |
| script/decode_provisioning_profile.sh | Decode provisioning profile via OpenSSL CMS. |
| script/classify_appcast_bootstrap.sh | Classify appcast bootstrap mode from tag history. |
| script/apply_github_protections.sh | Rebrand protections script + repo target. |
| script/embed_sparkle.sh | Rebrand Sparkle embedding commentary. |
| script/Runway.release.entitlements.plist | Add release entitlements for iCloud container. |
| script/Runway.local.entitlements.plist | Add local debug entitlements. |
| script/Runway.dev.entitlements.plist | Update dev entitlements + CloudKit. |
| script/lib/testflight_paths.mjs | Add TestFlight path relevance filter. |
| script/lib/testflight_paths.test.mjs | Add node tests for TestFlight path filter. |
| ios/RunwayMobile/RunwayMobileApp.swift | Add iOS companion app entrypoint. |
| ios/RunwayMobileWidgets/RunwayWidgetsBundle.swift | Add widgets bundle entrypoint. |
| ios/RunwayMobileWidgets/UsageConfigurationIntent.swift | Add widget configuration intent. |
| ios/RunwayMobile/Assets.xcassets/Contents.json | Add iOS asset catalog metadata. |
| ios/RunwayMobile/Assets.xcassets/AppIcon.appiconset/Contents.json | Add iOS app icon catalog metadata. |
| ios/Config/RunwayMobileWidgets.Release.entitlements | Add widgets release entitlements. |
| ios/Config/RunwayMobileWidgets.Debug.entitlements | Add widgets debug entitlements. |
| ios/Config/RunwayMobileWidgets-Info.plist | Add WidgetKit extension Info.plist. |
| ios/Config/RunwayMobile.Release.entitlements | Add iOS app release entitlements. |
| ios/Config/RunwayMobile.Debug.entitlements | Update iOS debug entitlements + CloudKit. |
| Sources/RunwayApp/RunwayApp.swift | New Runway @main app + Settings command override. |
| Sources/OpenUsageApp/OpenUsageApp.swift | Remove OpenUsage @main app entrypoint. |
| Sources/RunwayCLI/CLIArguments.swift | Add CLI argument parsing + error types. |
| Sources/RunwayCLI/AppBundleLocator.swift | Rebrand CLI defaults suite env var + bundle ID. |
| Sources/OpenUsage/Support/AppInfo.swift | Remove OpenUsage version helper. |
| Sources/Runway/Support/AppInfo.swift | Add Runway version helper with “dev” fallback. |
| Sources/Runway/Support/AboutPanel.swift | Rebrand About panel + repo link. |
| Sources/Runway/Support/AppNotifications.swift | Rebrand notification thread/id prefixes. |
| Sources/Runway/Support/AppShortcuts.swift | Update shortcut help text for Settings window. |
| Sources/Runway/Support/ContainingAppBundle.swift | Add helper to find containing .app bundle. |
| Sources/Runway/Support/LiquidGlassFallbacks.swift | Update comments for Settings window panes. |
| Sources/Runway/Support/MenuBarIcon.swift | Use Runway mark + updated icon shape init. |
| Sources/Runway/Support/MetricPeriod.swift | Centralize common period durations (ms). |
| Sources/Runway/Support/PopoverSurfaceTreatment.swift | Add environment value for surface treatment. |
| Sources/Runway/Services/CommandLineToolInstaller.swift | Rebrand helper binary + paths + messages. |
| Sources/Runway/Services/HTTPClient.swift | Rebrand proxy config path mention. |
| Sources/Runway/Services/LocalUsageServer.swift | Rebrand dispatch queue label + doc wording. |
| Sources/Runway/Services/ProxyConfig.swift | Rebrand config path + docs wording. |
| Sources/Runway/Services/ShellEnvironmentSnapshot.swift | Add Kimi env keys + rebrand storage key. |
| Sources/Runway/Stores/LayoutPersistence.swift | Remove expandedProviders persistence hooks. |
| Sources/Runway/Stores/LaunchAtLoginSetting.swift | Improve failure message wording. |
| Sources/Runway/Stores/NotificationSettingsStore.swift | Rebrand notification defaults keys. |
| Sources/Runway/Stores/OnboardingStore.swift | Rebrand onboarding defaults key. |
| Sources/Runway/Stores/PopoverNavigationStore.swift | Remove Settings from popover navigation. |
| Sources/Runway/Stores/QuotaNotificationEvaluator.swift | Add prune API; rebrand ISO formatter usage. |
| Sources/Runway/Stores/RefreshSetting.swift | Add shared fixed refresh cadence constants. |
| Sources/Runway/Stores/TimeFormatSetting.swift | Adjust doc comment wording. |
| Sources/Runway/Stores/TotalSpendSetting.swift | Add setting key for Total Spend card visibility. |
| Sources/Runway/Models/DashboardLayout.swift | Add placed-widget model for dashboard layout. |
| Sources/Runway/Models/DeviceSnapshotDocument.swift | Add CloudKit device snapshot payload model. |
| Sources/Runway/Models/LimitResourceDescriptor.swift | Add /v1/limits export descriptors. |
| Sources/Runway/Models/MenuBarStyle.swift | Add menu bar rendering style enum. |
| Sources/Runway/Models/MetricKind.swift | Adjust doc wording. |
| Sources/Runway/Models/MetricLine.swift | Add Connect badge + connect prompt classification. |
| Sources/Runway/Models/MetricValue.swift | Add labeled value selection mode. |
| Sources/Runway/Models/Provider.swift | Adjust provider links doc wording. |
| Sources/Runway/Models/ResetDisplayMode.swift | Adjust doc wording. |
| Sources/Runway/Models/UsageHistoryDescriptor.swift | Add explicit cross-device history scope metadata. |
| Sources/Runway/Models/UsageHistoryDocument.swift | Rebrand schemas + error text. |
| Sources/Runway/Models/WidgetDescriptor.swift | Add widget descriptor model + metadata fields. |
| Sources/Runway/Models/WidgetDescriptor+Factories.swift | Add subtitle value labels + pinnable toggle. |
| Sources/Runway/Models/WidgetDisplayMode.swift | Adjust doc wording. |
| Sources/Runway/Pricing/PricingCatalogCodecs.swift | Rebrand doc wording. |
| Sources/Runway/Providers/JSONLScanCacheCoordination.swift | Flush Grok persistent cache writes. |
| Sources/Runway/Providers/ProviderAuthRetry.swift | Reword doc comment. |
| Sources/Runway/Providers/ProviderUsageErrorText.swift | Add shared user-facing usage error copy. |
| Sources/Runway/Providers/SpendTileMapper.swift | Use Runway ISO8601 parser for day keys. |
| Sources/Runway/Providers/UsageLogReadFailureReporter.swift | Add edge-triggered unreadable-log reporter actor. |
| Sources/Runway/Providers/Antigravity/AntigravityErrors.swift | Add keychain connect/denial error variants. |
| Sources/Runway/Providers/Antigravity/AntigravityMetric.swift | Centralize Antigravity IDs/labels. |
| Sources/Runway/Providers/Antigravity/AntigravityUsageClient.swift | Update doc wording re: OAuth creds. |
| Sources/Runway/Providers/Antigravity/AntigravityUsageMapper.swift | Use Runway ISO8601 parsing for reset times. |
| Sources/Runway/Providers/Claude/ClaudeConfigDirDiscovery.swift | Switch Keychain protocol type + parse helper. |
| Sources/Runway/Providers/Claude/ClaudeUsageClient.swift | Add Claude usage client + shared error text. |
| Sources/Runway/Providers/Copilot/CopilotUsageClient.swift | Add Copilot internal usage client. |
| Sources/Runway/Providers/Cursor/CursorCSVParser.swift | Tighten CSV parser doc wording. |
| Sources/Runway/Providers/Cursor/CursorUsageClient.swift | Remove refresh-token endpoint path in client. |
| Sources/Runway/Providers/Cursor/CursorUsageCSV.swift | Clarify v1 cost modeling in docs. |
| Sources/Runway/Providers/Devin/DevinUsageClient.swift | Add Devin usage client. |
| Sources/Runway/Providers/Grok/GrokAuthStore.swift | Use Runway ISO8601 parsing. |
| Sources/Runway/Providers/Grok/GrokCreditsConfigDecoder.swift | Use Runway ISO8601 parsing; rebrand docs. |
| Sources/Runway/Providers/Grok/GrokUsageClient.swift | Rebrand User-Agent header. |
| Sources/Runway/Providers/OpenCode/OpenCodeUsageClient.swift | Add OpenCode Go usage client. |
| Sources/OpenUsage/Providers/OpenCode/OpenCodeUsageMapper.swift | Remove OpenUsage OpenCode usage mapper. |
| Sources/Runway/Providers/OpenRouter/OpenRouterAuthStore.swift | Rebrand config path in error + paths. |
| Sources/Runway/Providers/OpenRouter/OpenRouterUsageMapper.swift | Fix Key Limit to use current-window spend. |
| Sources/Runway/Providers/Pi/PiPaths.swift | Add pi session log directory resolver. |
| Sources/Runway/Providers/ZAI/ZAIAuthStore.swift | Rebrand config path in error + paths. |
| Sources/Runway/App/FirstRunSeeder.swift | Rebrand docs. |
| Sources/Runway/App/SingleInstanceGuard.swift | Rebrand docs. |
| Sources/Runway/App/SingleInstanceLock.swift | Rebrand lock directory name + docs. |
| Sources/Runway/Views/APIKeysSection.swift | Use compact density; interactive refresh call. |
| Sources/Runway/Views/ClosureMenuItem.swift | Expand doc comment to additional NSMenu uses. |
| Sources/Runway/Views/CopyFeedbackButton.swift | Add presence callback for layout reservation. |
| Sources/Runway/Views/CustomizeHintCard.swift | Rebrand welcome title. |
| Sources/Runway/Views/CustomizeRow.swift | Use compact density constant. |
| Sources/Runway/Views/ModelUsageDetail.swift | Use compact density constant. |
| Sources/Runway/Views/PopoverSourceNote.swift | Add shared source-note footer view. |
| Sources/Runway/Views/PopoverTopBar.swift | Key top bar off page screen; remove Settings case. |
| Sources/Runway/Views/ProviderCard.swift | Use compact density constant. |
| Sources/Runway/Views/ProviderListRow.swift | Use compact density constant. |
| Sources/Runway/Views/ShareCardChrome.swift | Rebrand watermark icon + tagline. |
| Sources/Runway/Views/ShareCardView.swift | Add exported error-card rendering support. |
| Sources/Runway/Views/TotalSpendShareCardView.swift | Add Total Spend share PNG renderer view. |
| Sources/Runway/Views/TransientPill.swift | Add shared transient pill UI component. |
| Sources/Runway/Views/UpdateBannerCard.swift | Rebrand update banner message. |
| Sources/Runway/Views/UsageSparkline.swift | Use compact density constant; tighten docs. |
| Sources/Runway/Views/UsageTrendDetail.swift | Tighten docs wording. |
| Sources/Runway/Resources/ProviderIcons/antigravity.svg | Add/replace provider icon asset. |
| Sources/Runway/Resources/ProviderIcons/cursor.svg | Add/replace provider icon asset. |
| Sources/Runway/Resources/ProviderIcons/opencode.svg | Add/replace provider icon asset. |
| Sources/Runway/Resources/ProviderIcons/openrouter.svg | Add/replace provider icon asset. |
| Sources/Runway/Resources/ProviderIcons/runway.svg | Add Runway brand icon asset. |
| Sources/Runway/Resources/ProviderIcons/zai.svg | Add/replace provider icon asset. |
| Tests/RunwayTests/ZAIQuotaValidationTests.swift | Update imports + error assertion semantics. |
| Tests/RunwayTests/WidgetZeroUsageTests.swift | Update import to Runway. |
| Tests/RunwayTests/WidgetUsagePeriodTests.swift | Update import to Runway. |
| Tests/RunwayTests/WidgetRegistryTests.swift | Update import + add account-card ordering test. |
| Tests/RunwayTests/WidgetPercentClampTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/WidgetMeterStyleTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/WidgetDataStorePlanTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/WidgetDataStoreNotificationTests.swift | Update import to Runway. |
| Tests/RunwayTests/UsageTrendTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/UsageReaderTests.swift | Update import to Runway. |
| Tests/RunwayTests/UsageHistoryRefreshTriggerTests.swift | Rename callback to onLocalStateChanged + suites. |
| Tests/RunwayTests/UsageHistoryDocumentTests.swift | Rebrand schema strings in tests. |
| Tests/RunwayTests/UsageHistoryClassificationTests.swift | Add Sakana descriptors + expected scopes. |
| Tests/RunwayTests/UsageHistoryAggregatorTests.swift | Update import to Runway. |
| Tests/RunwayTests/UIProfilerTests.swift | Add tests for disabled UI profiler behavior. |
| Tests/RunwayTests/TransientNoticeTests.swift | Update import to Runway. |
| Tests/RunwayTests/TotalSpendAggregatorTests.swift | Update import to Runway. |
| Tests/RunwayTests/StaleWhileRevalidateTests.swift | Rename callback to onLocalStateChanged + suites. |
| Tests/RunwayTests/StalenessLabelTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/SpendTileMapperTests.swift | Update import to Runway. |
| Tests/RunwayTests/SingleInstanceLockTests.swift | Rebrand lock temp paths. |
| Tests/RunwayTests/SingleInstanceGuardTests.swift | Update import to Runway. |
| Tests/RunwayTests/SettingsPaneTests.swift | Add raw-value stability tests. |
| Tests/RunwayTests/SecretCodeMatcherTests.swift | Update import to Runway. |
| Tests/RunwayTests/SakanaLayoutTests.swift | Add default placement regression test. |
| Tests/RunwayTests/ResetDisplayTests.swift | Rebrand docs + add @mainactor. |
| Tests/RunwayTests/ReorderGeometryTests.swift | Update import to Runway. |
| Tests/RunwayTests/RefreshWakeSignalTests.swift | Update import to Runway. |
| Tests/RunwayTests/RefreshSettingTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/ProxyConfigTests.swift | Rebrand proxy config path in docs. |
| Tests/RunwayTests/ProviderSnapshotCacheTests.swift | Add deferred persistPending behavior test. |
| Tests/RunwayTests/ProviderSectionHeaderTests.swift | Add tooltip affordance behavior tests. |
| Tests/RunwayTests/ProviderParseTests.swift | Update import to Runway. |
| Tests/RunwayTests/ProviderLinksTests.swift | Add providers; tighten doc wording. |
| Tests/RunwayTests/ProviderEnablementStoreTests.swift | Rebrand defaults keys + suite names. |
| Tests/RunwayTests/ProviderEnablementEnforcementTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/ProviderAuthRetryTests.swift | Update import to Runway. |
| Tests/RunwayTests/PopoverTransparencyStyleTests.swift | Update import to Runway. |
| Tests/RunwayTests/PopoverTransparencyStoreTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/PopoverSurfaceOpacityTests.swift | Update import to Runway. |
| Tests/RunwayTests/PopoverScreenTests.swift | Remove Settings screen expectations; rebrand suites. |
| Tests/RunwayTests/PopoverKeyReaderTests.swift | Update import to Runway. |
| Tests/RunwayTests/PiUsageScannerTests.swift | Rebrand ISO8601 helper usage + docs. |
| Tests/RunwayTests/PanelOutsideClickPolicyTests.swift | Add visualPanelRect tests. |
| Tests/RunwayTests/PanelGeometryTests.swift | Update import to Runway. |
| Tests/RunwayTests/PaceNotificationLogicTests.swift | Update import to Runway. |
| Tests/RunwayTests/OpenCodeLayoutTests.swift | Update import to Runway. |
| Tests/RunwayTests/OpenCodeAuthStoreTests.swift | Update import to Runway. |
| Tests/RunwayTests/NewProviderSeederTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/ModelUsageHoverTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/MockData.swift | Rebrand fixture docs. |
| Tests/RunwayTests/MetricFormatterTests.swift | Update import to Runway. |
| Tests/RunwayTests/MeterSeverityTests.swift | Update import to Runway. |
| Tests/RunwayTests/MenuBarStripTrimTests.swift | Update import to Runway. |
| Tests/RunwayTests/MenuBarPrivacyStoreTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/MenuBarBarsTests.swift | Tighten doc wording. |
| Tests/RunwayTests/LogRedactionTests.swift | Tighten doc wording. |
| Tests/RunwayTests/LogLevelSettingTests.swift | Tighten docs + rebrand suite names. |
| Tests/RunwayTests/LocalTextFileAccessorTests.swift | Rebrand temp directory name. |
| Tests/RunwayTests/LayoutBootstrapTests.swift | Add shouldPersistPins assertions + suite names. |
| Tests/RunwayTests/LaunchAtLoginSettingTests.swift | Update import to Runway. |
| Tests/RunwayTests/KimiLayoutTests.swift | Add default placement regression test. |
| Tests/RunwayTests/JSONLScannerTestSupport.swift | Add ChunkRecorder helper. |
| Tests/RunwayTests/ICloudTestSupport.swift | Add shared iCloud fixtures + defaults helper. |
| Tests/RunwayTests/GrokCreditsConfigTests.swift | Update import to Runway. |
| Tests/RunwayTests/GrokCreditsConfigFixtures.swift | Update import to Runway. |
| Tests/RunwayTests/GrokAuthStoreTests.swift | Rebrand ISO8601 helper usage. |
| Tests/RunwayTests/FirstRunSeederTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/FailureBackoffTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayTests/DevinProviderTests.swift | Adjust assertions; extend FakeSQLite protocol stub. |
| Tests/RunwayTests/DensitySettingTests.swift | Add tests pinning compact-only density. |
| Tests/RunwayTests/DashboardClockTests.swift | Add tests for dashboard clock start/stop. |
| Tests/RunwayTests/CursorCSVBoundaryTests.swift | Update import to Runway. |
| Tests/RunwayTests/CommandLineToolInstallerTests.swift | Rebrand helper bundle/binary names + symlink text. |
| Tests/RunwayTests/ClaudeScopedAuthStoreTests.swift | Update import to Runway. |
| Tests/RunwayTests/ClaudeConfigDirDiscoveryTests.swift | Update import to Runway. |
| Tests/RunwayTests/AppNotificationsTests.swift | Update import to Runway. |
| Tests/RunwayTests/AntigravityLayoutTests.swift | Rebrand suite names for defaults. |
| Tests/RunwayCLITests/CLIArgumentsTests.swift | Rebrand CLI module import. |
| Tests/OpenUsageTests/TelemetrySinkTests.swift | Remove OpenUsage telemetry sink tests. |
| Tests/OpenUsageTests/ProvisioningProfileScriptTests.swift | Remove OpenUsage provisioning profile script tests. |
| Tests/OpenUsageTests/ProviderMarksTests.swift | Remove OpenUsage provider marks tests. |
| Tests/OpenUsageTests/ProcessRunnerTests.swift | Remove OpenUsage process runner tests. |
| Tests/OpenUsageTests/OpenUsageISO8601Tests.swift | Remove OpenUsage ISO8601 tests. |
| Tests/OpenUsageTests/KeychainAccessorTests.swift | Remove OpenUsage keychain accessor tests. |
Review details
- Files reviewed: 133/680 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| RELEASE_COUNT=0 | ||
| ONLY_RELEASE_TAG="" | ||
|
|
||
| while IFS= read -r release_tag || [ -n "$release_tag" ]; do | ||
| [ -n "$release_tag" ] || continue |
| /// Locks `Application Support/Runway/<bundle id>.lock` — a directory the app already uses | ||
| /// for its own state, and stable no matter where the app bundle itself lives. |
TL;DR
Selective re-implementation of the OpenUsage commits since Runway #111 that still apply: Claude Desktop's account-prefixed token caches, Claude spend tiles without an OAuth login, Grok subagent session ledgers, Codex Business Premium, Cursor's Models/Other Models labels, and new model rates.
What was happening
acct:<user>|<legacy key>(openusage Fix Claude Desktop Account-Prefixed Credential Caches #1212). Runway expected the client UUID first and skipped those entries, so a Desktop-only login showed Not logged in.subagent*session (openusage fix(grok): include subagent session usage #1193). Child work that the coordinator turn did not include disappeared from spend.self_serve_business_proliteentitlement rendered as "Self Serve Business Prolite" (openusage fix(codex): recognize Business Premium entitlement #1194).What this changes
acct:<user>|prefix, keeps only the signed-in account (fromlastKnownAccountUuid), and lets a scoped tombstone suppress the matching legacy V1 alias.updates.jsonlledger. Prompt-id dedup still drops forked parent replays.summary.jsonis no longer required to keep a ledger.self_serve_business_proliteto Business Premium.grok-bot-*→ Grok 4.6.Heads-up
Reviewed and not ported, with reasons:
ReorderFrameStore, parsed-onceProviderMark, and the rebuilt popover path. Taking their patch would duplicate that work.grok-bot-*pricing aliases.used <= 0,Pace.evaluatereturns nil when unused).Gemini 3.8 Flash output is $3.50, from Cursor's table, not upstream's $3.75 Google API rate. That matches how Runway priced Gemini 3.7.
Tests
acct:keys are ignored; a scoped V2 tombstone suppresses the V1 alias;load()readslastKnownAccountUuid.self_serve_business_prolite→ Business Premium, weekly-only window.testEveryAliasCanonicalResolvescovers the new rules.swift test --filter "ClaudeDesktopAuthStoreTests|ClaudeProviderTests|CursorProviderTests|CursorUsageSummaryTests|GrokLogUsageScannerTests|CodexUsageMapperTests|PricingBundledResourceTests|LayoutStoreTests"— 179 tests, 1 skipped, 0 failures.