Skip to content

fix(i18n): localize provider usage details in Simplified Chinese - #3084

Merged
steipete merged 2 commits into
steipete:mainfrom
haixing23:fix/zh-hans-provider-details
Aug 19, 2026
Merged

fix(i18n): localize provider usage details in Simplified Chinese#3084
steipete merged 2 commits into
steipete:mainfrom
haixing23:fix/zh-hans-provider-details

Conversation

@haixing23

Copy link
Copy Markdown
Contributor

Summary

  • localize provider-detail section titles, row labels, chart titles, and chart units at the presentation boundary
  • add Simplified Chinese coverage for DeepSeek detailed usage and balance components
  • add Simplified Chinese coverage for z.ai/GLM quota details, dynamic used/remaining values, token charts, and the textual 5-hour reset
  • preserve model names, API identifiers, numeric values, provider fetch/parsing logic, quota calculations, and all snapshot data

This follows the earlier Simplified Chinese localization work in #1059 and #1092. The provider snapshots/plugins continue to expose canonical English semantic labels; localization happens only when building the menu presentation.

Examples

DeepSeek now renders labels such as 用量明细, 今日, 本月, 缓存命中输入, and 每日 token. z.ai/GLM now renders 配额详情, 会话 Token 配额, 已使用 45%, 上限 1000 · 剩余 936, 每小时 token, and 每 5 小时重置. Technical identifiers such as GLM-5.3, deepseek-v4-flash, and search-prime remain unchanged.

Verification

  • swift test --filter MenuCardDeepSeekTests — 11 tests passed
  • swift test --filter ZaiMenuCardTests — 5 tests passed
  • swift test --filter UserFacingLocalizationCoverageTests — 6 tests passed
  • swift build — passed
  • app locale checker — passed for 23 locales
  • SwiftFormat lint — passed
  • SwiftLint --strict — 0 violations across 1,957 files
  • TypeScript plugin check — passed with an isolated typeRoots because this workstation has unrelated parent-level @types/node without undici-types

A debug build was launched against the existing provider configuration and the status menu opened successfully. Automated model assertions cover every translated value because macOS Screen Recording permission was unavailable for a reliable visual capture.

Audit notes

A broader audit found additional likely untranslated iCloud settings strings and provider labels. They are intentionally deferred so this PR remains limited to provider-detail presentation and the reported DeepSeek/z.ai surfaces.

@clawsweeper

clawsweeper Bot commented Aug 19, 2026

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2244846489

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +35 to +41
guard let paidRange = description.range(of: paidSeparator),
let grantedRange = description.range(
of: grantedSeparator,
range: paidRange.upperBound..<description.endIndex),
description.last == ")"
else {
return description

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Translate DeepSeek's unavailable balance states

When a DeepSeek account has no balance or API access is unavailable, DeepSeekUsageFetcher.swift constructs "¥0.00 — add credits at platform.deepseek.com" or "Balance unavailable for API calls". This guard only recognizes the paid/granted form, so those important status messages remain English under Simplified Chinese; handle and localize these alternate balance descriptions as well.

Useful? React with 👍 / 👎.

Comment on lines +82 to +87
let limitSeparator = " limit · "
let remainingSuffix = " remaining"
guard let limitRange = value.range(of: limitSeparator),
value.hasSuffix(remainingSuffix)
else {
return value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Translate one-sided z.ai quota amounts

When the z.ai response supplies only usage or only remaining—both fields are independently optional in zai.js—the plugin emits a secondary value such as "1000 limit" or "936 remaining". This guard recognizes only the combined "1000 limit · 936 remaining" form and returns either one-sided form unchanged, leaving part of the Simplified Chinese quota card in English.

Useful? React with 👍 / 👎.

Comment on lines +63 to +64
case .zai:
self.localizedZaiValue(value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Localize the z.ai credit-plan rate row

For every z.ai CREDIT_LIMIT plan, zai.js appends a Quota rate row whose value is Peak or Off-peak and whose secondary text is a dynamic English countdown. The z.ai localization path does not recognize any of these values, and no Simplified Chinese key was added for the row label, so credit-plan users still receive an entirely English detail row.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 19, 2026
@clawsweeper

clawsweeper Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed August 19, 2026, 6:33 PM ET / 22:33 UTC.

ClawSweeper review

What this changes

This PR localizes Simplified Chinese provider-detail labels, charts, quota values, and reset text for DeepSeek and z.ai usage cards.

Merge readiness

Blocked until stronger real behavior proof is added - 7 items remain

Keep this PR open: its presentation-boundary approach is sound, but three valid DeepSeek/z.ai output variants still remain English under zh-Hans.

Priority: P3
Reviewed head: 0f8fe0374d2f78d828e4388aae0f8ec88786aa46

Review scores

Measure Result What it means
Overall readiness 🧂 unranked krab (1/6) The patch is focused but has concrete localization gaps and lacks inspectable real-behavior proof.
Proof confidence 🧂 unranked krab (1/6) Needs stronger real behavior proof before merge: The PR reports tests and a local launch but supplies no inspectable after-fix runtime artifact; attach redacted menu proof or a live terminal transcript after addressing the findings. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦐 gold shrimp (3/6) 3 actionable review findings remain.

Verification

Check Result Evidence
Real behavior Needs proof Needs stronger real behavior proof before merge: The PR reports tests and a local launch but supplies no inspectable after-fix runtime artifact; attach redacted menu proof or a live terminal transcript after addressing the findings. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 5 items Current main does not contain this localization path: The current-main menu-card detail builder passes provider detail sections through without the PR's provider-detail localization helper, so the central requested behavior is not already implemented.
DeepSeek alternate states remain untranslated: The new formatter returns unchanged text unless it exactly matches the paid/granted balance form, while the provider emits zero-balance and unavailable API messages as valid alternatives.
z.ai one-sided quota values remain untranslated: The z.ai plugin independently emits limit and remaining fields, but the new formatter only recognizes their combined form and otherwise returns the English value unchanged.
Findings 3 actionable findings [P2] Translate DeepSeek’s unavailable balance states
[P2] Translate one-sided z.ai quota amounts
[P2] Localize the z.ai credit-plan rate row
Security None None.

Live Verification

Command: swift test --filter ZaiMenuCardTests

Result: FAIL (failed) — execution before step 1 run: sh -lc pnpm install --ignore-scripts --frozen-lockfile failed: ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-11.22.0.tgz

sh -lc pnpm install --ignore-scripts --frozen-lockfile failed: ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-11.22.0.tgz

Assertions:

  • FAIL expect_output: Test run with

How this fits together

CodexBar converts provider snapshots into menu-card rows, charts, and metric text. The changed model layer localizes those presentation values before the native menu renders them.

flowchart LR
A[Provider snapshots] --> B[Menu card model]
B --> C[Provider detail sections]
D[Localized string resources] --> E[Presentation localization]
C --> E
E --> F[Native menu card]
Loading

Before merge

  • Add real behavior proof - Needs stronger real behavior proof before merge: The PR reports tests and a local launch but supplies no inspectable after-fix runtime artifact; attach redacted menu proof or a live terminal transcript after addressing the findings. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Translate DeepSeek’s unavailable balance states (P2) - The formatter only recognizes the paid/granted form, but DeepSeekUsageSnapshot also emits zero-balance and unavailable-API descriptions. Those valid states bypass this guard and remain English under zh-Hans; add localized forms and model coverage.
  • Translate one-sided z.ai quota amounts (P2) - The z.ai plugin emits N limit and N remaining independently when either field is absent. This combined-form guard returns both valid one-sided variants unchanged, leaving the quota card partly English.
  • Localize the z.ai credit-plan rate row (P2) - Credit plans add Quota rate, Peak/Off-peak, and a dynamic countdown, but this path has neither catalog entries nor z.ai value handling. The entire row remains English for those users.
  • Resolve merge risk (P1) - Merging now leaves valid DeepSeek balance states and z.ai quota/credit-plan rows partly English for Simplified Chinese users.
  • Resolve merge risk (P1) - No inspectable after-fix native-menu evidence accompanies the claimed local launch.
  • Complete next step (P2) - The code gaps are narrow, but the external contributor must also provide real after-fix proof before this PR can be merged.

Findings

  • [P2] Translate DeepSeek’s unavailable balance states — Sources/CodexBar/MenuCardView+ProviderDetailLocalization.swift:35-41
  • [P2] Translate one-sided z.ai quota amounts — Sources/CodexBar/MenuCardView+ProviderDetailLocalization.swift:82-87
  • [P2] Localize the z.ai credit-plan rate row — Sources/CodexBar/MenuCardView+ProviderDetailLocalization.swift:63-64
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Implementation versus tests production +118, tests +185 The focused test growth is proportionate, but the untested dynamic variants still need coverage.

Merge-risk options

Maintainer options:

  1. Decide the mitigation before merge
    Complete the narrow presentation translator and zh-Hans catalog for every known DeepSeek and z.ai dynamic variant, then attach redacted after-fix menu or terminal evidence.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Technical review

Best possible solution:

Complete the narrow presentation translator and zh-Hans catalog for every known DeepSeek and z.ai dynamic variant, then attach redacted after-fix menu or terminal evidence.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: valid provider-produced strings bypass the new guards and remain English under zh-Hans; the PR's existing model-test seam can exercise them.

Is this the best way to solve the issue?

No; localizing at the presentation boundary is appropriate, but the implementation must cover all currently emitted DeepSeek and z.ai variants.

Full review comments:

  • [P2] Translate DeepSeek’s unavailable balance states — Sources/CodexBar/MenuCardView+ProviderDetailLocalization.swift:35-41
    The formatter only recognizes the paid/granted form, but DeepSeekUsageSnapshot also emits zero-balance and unavailable-API descriptions. Those valid states bypass this guard and remain English under zh-Hans; add localized forms and model coverage.
    Confidence: 0.99
  • [P2] Translate one-sided z.ai quota amounts — Sources/CodexBar/MenuCardView+ProviderDetailLocalization.swift:82-87
    The z.ai plugin emits N limit and N remaining independently when either field is absent. This combined-form guard returns both valid one-sided variants unchanged, leaving the quota card partly English.
    Confidence: 0.99
  • [P2] Localize the z.ai credit-plan rate row — Sources/CodexBar/MenuCardView+ProviderDetailLocalization.swift:63-64
    Credit plans add Quota rate, Peak/Off-peak, and a dynamic countdown, but this path has neither catalog entries nor z.ai value handling. The entire row remains English for those users.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.97

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 0caf970a5061.

Labels

Label changes:

  • add P3: This is a focused localization improvement with limited functional blast radius.
  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦐 gold shrimp.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR reports tests and a local launch but supplies no inspectable after-fix runtime artifact; attach redacted menu proof or a live terminal transcript after addressing the findings. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P3: This is a focused localization improvement with limited functional blast radius.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR reports tests and a local launch but supplies no inspectable after-fix runtime artifact; attach redacted menu proof or a live terminal transcript after addressing the findings. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

What I checked:

Likely related people:

  • steipete: Authored the prior merged provider-detail Simplified Chinese localization work and is the current-tree blame author on the surrounding provider presentation paths. (role: prior feature owner and recent area contributor; confidence: medium; commits: bef9269ea97c, 0f8fe0374d2f; files: Sources/CodexBar/MenuCardView.swift, Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings, Sources/CodexBarCore/Resources/Plugins/zai.js)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Cover the three known provider-generated variants with focused model tests.
  • Attach redacted after-fix menu or terminal proof, then request a fresh review.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@steipete
steipete merged commit 5878c4e into steipete:main Aug 19, 2026
1 check passed
steipete added a commit that referenced this pull request Aug 19, 2026
steipete added a commit that referenced this pull request Aug 20, 2026
sf-jin-ku added a commit to sf-jin-ku/CodexBar that referenced this pull request Aug 20, 2026
…tails

SwiftFormat rejected the nested guard wrap, and OpenRouter detail titles now follow the steipete#3084 localization pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
sf-jin-ku added a commit to sf-jin-ku/CodexBar that referenced this pull request Aug 20, 2026
Provider detail titles and catalogued labels go through L after steipete#3084, so the popup test must assert the Traditional Chinese strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
sf-jin-ku added a commit to sf-jin-ku/CodexBar that referenced this pull request Aug 20, 2026
…ails

SwiftFormat rejected the optional-chain wrap, and OpenRouter detail titles now follow the steipete#3084 localization pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
steipete added a commit that referenced this pull request Aug 20, 2026
* fix(claude): keep at-limit claude-swap cards complete

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: cite at-limit claude-swap card PR

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): keep non-limit claude-swap sentinels metrics-less

Project usage only for ok and unavailable slots, and retain a previous snapshot only when the email still matches and a 100% window has not reset.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): drop expired windows from retained at-limit snapshots

Keeping the whole previous snapshot after any sibling reset still showed
"Resets now" on lanes that had already recovered.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): retain at-limit swap windows in CLI and dashboard

One-shot cards and dashboard now reuse the last slot windows so an
unavailable cswap row with null usage keeps the exhausted bars.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): bind retained at-limit windows to the slot account

A SHA-256 fingerprint in the retained-usage cache rejects leftover 100% bars after the same slot is reused by a different account.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): prune expired windows on attached unavailable snapshots

Direct cswap unavailable payloads with mixed reset times now drop already-reset lanes the same way retained snapshots do.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): keep retained at-limit bars on dashboard and app restart

Seed the app projection from the retained-usage cache after a relaunch, and render dashboard windows alongside the deferred/limit note instead of returning after the error.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): require an email before retaining at-limit windows

Slot-only fingerprints reused bars after an email-less account was replaced. Decline persistence and reuse unless the row has an email, and move the changelog note to 0.54.1 Unreleased.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): save retained usage only for current refreshes

Stale claude-swap refreshes were writing the on-disk cache before the generation guard, and 100% windows without a future reset were kept forever.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): flatten fingerprint matching and expect localized API key details

SwiftFormat rejected the nested guard wrap, and OpenRouter detail titles now follow the #3084 localization pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): drop retained windows without a future reset

A non-exhausted session lane with no resetsAt was kept beside a weekly at-limit bar and re-saved until that weekly reset, so a five-hour value could linger for days.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
steipete added a commit that referenced this pull request Aug 20, 2026
* feat(kiro): show overage credits against their cap

kiro-cli /usage states plan credits alone and omits the overage section
for organization accounts, so a spent plan looks like the account is out.
Read GetUsageLimits with the CLI's own token (read-only) and surface
overage as a second credit window plus charges against the overage budget.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): reject impossible usage counters from GetUsageLimits

An overage larger than total usage would clamp planUsed to zero and
overwrite valid CLI numbers. Honor API-disabled overage over a stale
CLI Enabled line so enrichment cannot resurrect a cap the API says is off.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): reject GetUsageLimits payloads that exceed the plan

Drop enrichment when plan usage is above the plan ceiling, and treat an unrecognized overage status as unknown so the CLI overage line can still stand.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: import FoundationNetworking for Linux URLSession types

* fix(kiro): resolve the CLI state database on Linux

GetUsageLimits enrichment always looked under macOS Application Support,
so Linux refreshes never found data.sqlite3.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): reject overage above cap and honor API currency

Best-effort GetUsageLimits data should not present a spend window
above its ceiling or format non-USD charges as dollars.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): propagate cancellation from usage-limits enrichment

A cancelled GetUsageLimits call now fails the refresh instead of publishing a CLI-only snapshot as success.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): drop CLI USD overage estimate when the API currency is not USD

A missing overageCharges field no longer lets a dollar CLI fallback render in the API's non-USD currency.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): add locale entries for overage copy

Translate the new Overage window title and Overage credits left detail row in every app catalog so non-English UIs no longer fall back to English.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): honor API-disabled overage and localize cap phrases

GetUsageLimits DISABLED now replaces a stale CLI Enabled status, and Kiro "of N" detail values go through the of %@ localization key.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(i18n): expect localized OpenRouter API key details in zh-Hant

Provider detail titles and catalogued labels go through L after #3084, so the popup test must assert the Traditional Chinese strings.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): clamp overage that runs slightly past the cap

Rejecting over-cap counters dropped the whole GetUsageLimits payload and hid overage on organization accounts, so the gauge now clamps to the cap instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): keep over-cap overage and localize credit units

Clamping hid the amount Kiro actually billed, and overage usage still rendered the English "credits" suffix in other locales.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kiro): keep CLI overage when API omits the cap

ENABLED without overageCapWithPrecision is incomplete enrichment, not a disabled account.

* fix(kiro): mark API-enabled overage when the CLI omits it

Organization accounts skip the CLI overage section, so the menu-bar modes never saw an Enabled row.

* fix(kiro): keep CLI plan usage when API bonuses are present

GetUsageLimits folds bonus spend into currentUsage, so overwriting the plan gauge would double-count bonus credits.

* fix(kiro): parse bonus-inclusive usage above the plan limit

GetUsageLimits folds bonus spend into currentUsage, so rejecting planUsed > planLimit dropped overage enrichment for those accounts.

* fix: preserve stacked menu bar layout line breaks (#3094)

* Preserve card menu-item subclass during cached swaps (#3093)

* Preserve menu item subclasses during cached swaps

* Add native menu proof for cached shell swaps

---------

Co-authored-by: Kiran Magic <262980978+kiranmagic7@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* docs: changelog for #3093

* Extend menu bar conditionals beyond usage percentages (#3088)

* Extend menu bar conditionals beyond usage percentages

Conditional predicates could only compare four percent-used windows. They now
compare 18 metrics across four units: percent windows, the direct
primary/secondary/tertiary lanes, four reset countdowns, three pace deltas,
run-out, credit balance, and today/30-day cost. Metrics with two readings
(percent windows, lanes, balance) gain a used/remaining select, so
"session > 50% used and session resets in < 2h" is expressible.

Pace, run-out, balance and cost were only carried as display strings, which
cannot be compared, so MenuBarLayoutRenderMetrics carries their numeric twins
pre-rounded to the same granularity as the text they mirror.

Three refresh gates needed widening for the new data dependencies:
- The title cache key had no component that moves with the clock, so a
  countdown predicate would have served its pre-flip title indefinitely. It now
  keys on the per-conditional outcome, evaluated once per render.
- The four observation signatures gated on display tokens; a predicate on cost
  or balance has no token. They now also read the conditionals' metrics, which
  additionally fixes lane tokens inside conditional branches being invisible to
  the lane signature.
- A reset-countdown predicate flips at an instant nothing else ticks on, so the
  countdown scheduler wakes at `resetsAt - threshold`.

The conditional library is now decoded element-wise: this change makes
forward-incompatible metric values possible for the first time, and one unknown
value would otherwise have wiped the whole library on a downgrade.

Ships an "Auto % / Resets in" default that renders the automatic percentage
while the lane has headroom and the reset countdown once it is spent.

* Sign the readings conditional predicates actually compare

Three observation-signature gaps let a predicate flip without a redraw:

- Cost signatures recorded only the currency-formatted string, so two token-cost
  updates could cross a threshold while both formatted to the same cent. A
  referenced cost metric now signs the unrounded amount losslessly.
- The balance signature recorded only the rendered "Remaining" row, so a
  `balance used` predicate — which reads the "Used" row no token surfaces — was
  entirely unsigned. Both amounts are now signed.
- The lane signature recorded the displayed reading, which follows
  `usageBarsShowUsed` and clamps remaining at zero, while `RateWindow.usedPercent`
  deliberately preserves over-quota values. A used-direction predicate such as
  `primaryLane > 105%` could move 104% -> 106% against a constant `0.000`.

The lane signature is now scoped to what the layout renders, and a new
conditional-window signature covers what conditionals read: the raw used percent
(which remaining derives from, so it covers both directions) plus `resetsAt`,
which countdown predicates depend on and no display token contributes.

* Tick clock-derived predicates that no token schedules

`menuBarWeeklyPaceRefreshDelays` is gated on a placed `.pace(.weekly)` token and
only wakes once, at the pace-eligibility boundary. Excluding `runsOutIn` from the
conditional reset schedule on the assumption that scheduler covered it therefore
left a hole: a layout whose only pace or run-out reference is a predicate got no
clock wake-up at all, so it kept rendering the branch that was true when the
value last moved.

Referenced weekly-pace predicates now also trigger the eligibility wake-up, and
any referenced pace or run-out predicate schedules a minute tick. Both numbers
are pre-rounded to the granularity the menu bar shows -- whole percentage points
and whole minutes -- so a minute tick is exactly enough, and it is the cadence a
`.resetCountdown` token already costs. Money predicates deliberately schedule
nothing: they move only when new provider data arrives.

* Keep older releases' conditional libraries readable on downgrade

Decoding the library element-wise only helps builds that already have the
lenient decoder. The build a user actually downgrades to decodes
`menuBarLayoutConditionals` strictly and falls back to `[]`, so one saved rule
using a new metric would empty the entire library there.

The conditional library now dual-writes the way layouts already do:
`menuBarLayoutConditionalsV2` keeps full fidelity, and the original key keeps an
older-readable projection. `loadLibrary` mirrors `preferredLayout` — the current
key wins unless the legacy key disagrees with its own projection, which only
happens when an older release wrote it, and that edit must survive.

The projection drops an entry when any clause uses a metric outside the original
four, and also when any clause uses a non-`.used` direction. The second case is
the subtler one: an older release's synthesized decoder ignores the unknown
`direction` key, so `session remaining > 80` would come back as
`session used > 80` and render the opposite branch. A missing rule is visibly
missing; an inverted one is not.

* Drop cost metrics that could not be converted to USD

`UsageFormatter.convertedCost` returns the source amount unchanged when it has no
rate for the provider's currency, and both cost producers passed that value
straight through as `costTodayUSD`/`cost30dUSD`. A `Cost today > 5 USD` rule
would then compare, say, €6 against a $5 threshold and pick the wrong branch.

Both producers now keep the amount only when the conversion actually landed in
USD. Otherwise the predicate sees no value and evaluates false, which is the
existing contract for a metric the provider does not report. The rendered text is
untouched and still shows the provider's own currency.

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* Prevent RPC pipe writes from aborting after child exit (#3095)

* fix: prevent RPC pipe writes from aborting after child exit

* test: repin UsageFetcher codex identity anchor after pipe-write refactor

* Publish live Grok tokens and xAI spend into Usage & Spend (#3085)

* Publish live Grok tokens and xAI spend into Usage & Spend

Enabled Grok and xAI now join the shared spend catalog instead of only
inflating the unavailable denominator. xAI contributes vendor-metered
daily USD from the Management API chart; Grok contributes local session
tokens. SuperGrok credits and xAI prepaid balance stay quotas, not spend.

* docs: add #3085 to changelog

* Fix lint on Grok and xAI spend messages

Wrap no-data copy under 120 characters and drop a redundant throws
on the prepaid-balance mapping test.

* Fix Grok/xAI spend publication, Today, and coverage

Preserve xAI analytics failures as unavailable instead of known-zero
spend, publish local Grok tokens when remote billing fails, pin Today
to the current UTC/local day, and keep xAI history as a 30-day source.

* Fix Grok and xAI spend edge cases

* Fix OpenRouter localization test after #3086

#3086 scoped localizedProviderDetails to DeepSeek and z.ai, so generic
OpenRouter details keep canonical English. The merge test still expected
zh-Hant "API 金鑰" and failed macOS shard 0.

* Align OpenRouter localization test with main

Peter restored generic title/row L() localization in 84a4ca7 after
#3086 scoped it away. The merge kept the canonical-English assertion
from the earlier CI fix; match the restored shared catalog instead.

* test: reconcile gatekeeper anchors and fingerprints with Grok/xAI spend clusters

* test: include Grok and xAI in the cost-capable dashboard source contract

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* docs: credit #3085 and #3088 changelog entries

* chore: finalize 0.54.0 changelog and bump build to 127

* docs: update appcast for 0.54.0

* chore: open 0.54.1 unreleased changelog section

* Clarify five-hour quota wording in Simplified Chinese (#3070)

* Clarify Simplified Chinese five-hour quota label

* Derive Simplified Chinese session quota labels from duration

---------

Co-authored-by: UNGETSU <ungetsu@UNGETSUdeMacBook-Air.local>

* Fix agent session menu width (#3096)

* fix(alibaba): resolve mainland Personal/Solo sec_token from the console shell (#3098)

Mainland Personal/Solo Token Plan (cn-personal) fails with a 200
`BailianGateway.Login.NotLogined` body ("Alibaba Token Plan login required")
even with fresh, valid aliyun cookies, because the request lacks the
`sec_token` the OneConsole gateway requires. #2533 already forwards the token
when present, but it was never resolvable for this path for two reasons:

- The console shell only server-renders `window.ALIYUN_CONSOLE_CONFIG.SEC_TOKEN`
  for a genuine same-origin document navigation; a bare GET receives a
  token-less shell. Send the browser-navigation headers (Referer, Sec-Fetch-*,
  Accept-Language) so the shell includes the token.
- The shell embeds it as an upper-case, unquoted key (`SEC_TOKEN: "..."`), but
  `extractSECToken` only matched the lower-case `secToken`/`sec_token` shapes.
  Add the `SEC_TOKEN` pattern.

With both, the scraper resolves the Personal `sec_token`, the gateway returns
real usage, and the mainland Personal/Solo card renders. Verified end-to-end
on a real cn-personal account: `secTokenSource=resolved`, body `message=Success`,
5-hour/weekly windows populated (was "login required").

Adds AlibabaTokenPlanSECTokenScrapeTests covering the upper-case shell format,
the existing lower-case shapes, and the no-token case.

Refs #2500, #2349, #2370, #2533.

Co-authored-by: LeoLin <leolin990405@gmail.com>

* Add BigModel CN account balance to z.ai provider (#3109)

* Add BigModel CN account balance to z.ai provider

* Bound balance timeout, region-aware override validation, null-safe amounts

---------

Co-authored-by: RunhuaHuang <RunhuaHuang@users.noreply.github.com>

* docs: credit #3070 #3096 #3098 #3109 changelog entries

* test: repair gatekeeper anchors after #3096 menu refactor

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: kiranmagic7 <kiranmagic@proton.me>
Co-authored-by: Kiran Magic <262980978+kiranmagic7@users.noreply.github.com>
Co-authored-by: William Mitchell <13799071+wdmitchelluk@users.noreply.github.com>
Co-authored-by: Alec Gutman, Chip <44984861+Chipagosfinest@users.noreply.github.com>
Co-authored-by: UNGETSU <44204222+YunyueLi@users.noreply.github.com>
Co-authored-by: UNGETSU <ungetsu@UNGETSUdeMacBook-Air.local>
Co-authored-by: KaranocaVe <39995482+KaranocaVe@users.noreply.github.com>
Co-authored-by: Zhongyue Lin <101193087+LeoLin990405@users.noreply.github.com>
Co-authored-by: LeoLin <leolin990405@gmail.com>
Co-authored-by: Xigua <rh2897@columbia.edu>
Co-authored-by: RunhuaHuang <RunhuaHuang@users.noreply.github.com>
steipete added a commit that referenced this pull request Aug 20, 2026
…3082)

* fix(claude): disambiguate claude-swap accounts that share an email

Show organization name, slot, or a user alias on cards that share an email, while keeping identity on the numeric slot and the raw address.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): keep swap display labels off token-account identity

Prefer Claude swap aliases and org suffixes on Claude cards only, and keep the raw email as a separate account field so failed dashboard rows still identify without colliding stacked token accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): sync claude-swap cards by raw email, not alias

iCloud labels were taking the UI display string, which can be an alias or email · org. Persist the source address instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): redact email-shaped aliases and omit org from swap identity

Dashboard redaction leaked aliases that looked like emails, and CloudKit
sync persisted display-only organization names on the usage identity.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(claude): assert swap org names stay out of sync payloads

CloudKit serializes the usage snapshot, so keep a regression that
organization suffixes never enter the Codable identity.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): keep swap labels and org emails off persisted identity

Scope the display-label override to claude-swap cards, omit aliases
when a slot has no email, and redact email-shaped org suffixes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): key duplicate swap slots by source identity in cloud sync

Usage identity now carries claude-swap:<slot> so shared emails no longer collapse into one CloudKit record, and the menu-card regression test passes the swap source label.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): keep non-email alias text when redacting dashboard labels

Token-wise redaction now applies to mixed aliases such as Work (owner@example.com) instead of collapsing the whole label to redacted@domain.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): disambiguate colliding orgs and redact emails inside aliases

Append Account N when duplicate-email rows share an organization name, replace email ranges in dashboard aliases instead of whole tokens, and fix MenuCardView brace formatting that failed CI lint.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): keep menu email helper within lint limits

Pass the card input into the email helper so SwiftLint stays under the parameter cap, retarget gatekeeper anchors, and move the changelog note to 0.54.1 Unreleased.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): correct cursor-email indent and expect localized API key details

SwiftFormat rejected the optional-chain wrap, and OpenRouter detail titles now follow the #3084 localization pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): rewrite the cursor email fallback without wrapped conditions

SwiftFormat on CI still rejected the comma-separated if wrap around the Cursor subject ID.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude): redact internal dashboard emails and shorten cursor fallback

Hide Personal Info missed owner@corp and domain-literal addresses, and the cursor subject-ID line tripped CI line length.

* fix(claude): redact apostrophe emails and split alias tests

Hide Personal Info was matching past apostrophes in local parts, and the merged projection tests exceeded SwiftLint's type body warning.

* fix(claude): redact quoted local parts in dashboard identity

Quoted addresses such as "owner"@example.com were left intact because quotes were treated as email boundaries.

* fix(claude): redact quoted local parts that contain spaces

Whitespace inside `"owner smith"@example.com` was treated as a token boundary and leaked the unquoted prefix.

* fix(claude): treat same-mailbox emails as duplicates ignoring case

Disambiguation counted Shared@example.com and shared@example.com as distinct, so neither card got an org suffix.

* fix(claude): redact slash-separated dashboard emails independently

A slash was not an address boundary, so owner@example.com/backup@example.net collapsed to a single redacted domain.

* fix(claude): count colliding labels case-insensitively

Same-mailbox cards that differ only by email casing still need Account N when they share an organization name.

* fix(claude): fingerprint retained usage from the mailbox not the alias

Display-only aliases were hashed into claude-swap-retained-usage.json when the slot had no email, which could not match later rows.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants