Skip to content

chore: sync fork with upstream (#357, #359, #360, #361) - #73

Merged
cte13 merged 5 commits into
mainfrom
chore/upstream-sync-3
Sep 26, 2026
Merged

cte13 merged 5 commits into
mainfrom
chore/upstream-sync-3

Conversation

@cte13

@cte13 cte13 commented Sep 26, 2026

Copy link
Copy Markdown
Owner

⚠️ Merge with "Create a merge commit", not squash. Squashing breaks ancestry with upstream and leaves phantom "commits behind".

Summary

Brings in 4 upstream PRs:

Upstream PR Change
chattymin#357 feat(menubar): color limit percentages like the popover gauges
chattymin#359 fix: stop the test suite from reading and rewriting the user's usage cache
chattymin#360 fix: stop unchanged status lines from filling the log every poll
chattymin#361 fix: log each unpriced model once so a missing price row is noticed

Conflict resolution

chattymin#359 and chattymin#360 are the upstreamed forms of the fork's own #22 and #29, so nearly all 9 conflicted files were the same code with reworded (now English) comments. Upstream's side was taken in those files. Test sets were compared first: LogRepeatSuppressorTests has the same 6 tests on both sides, so no fork-only test was dropped.

Fork-only behavior kept:

  • Antigravity manual-refresh failure message (UsageStore catch block): the fork-only antigravityLimitRefreshFailure, which upstream doesn't have.
  • OpenCode Go menu-bar item: feat(menubar): color limit percentages like the popover gauges chattymin/PokeTokenBar#357 changed menuLimitParts from strings to MenuLimitPart. The fork-only OpenCode entry now goes through the same add() helper, so it gets gauge colors too (no pace marker, like the Antigravity and Cursor items).
  • Duplicate persistsToDisk assignment: auto-merge kept the fork's copy next to upstream's, and the compiler rejected it. The fork's copy was dropped. Every file touched by both sides was then scanned for other statements duplicated by the merge; none were found.

Test plan

🤖 Generated with Claude Code

https://claude.ai/code/session_01PNenigaFnwvcTs2kDND16Z

cte13 and others added 5 commits September 26, 2026 20:24
…hattymin#361)

A model with no row in ModelPricing.table renders its cost as "Unavailable"
with no other trace, so a newly released model (Opus 5.5, fixed in chattymin#354) was
only noticed from a screenshot. Log each unpriced model identity once per
process from LocalUsageReader.Bucket.add ("Unpriced model: <id> — its cost
shows as Unavailable until ModelPricing.table has a row"). Rows whose source
already marks the cost as unavailable are not reported: a missing table row
is not the cause there.

Tests: the once-per-identity dedupe (normalized like the price lookup), and
the wiring observed through that dedupe, since AppLog is a no-op under swift
test. Fault injection: removing the report, or reporting source-unavailable
rows too, each fails the wiring test.


Claude-Session: https://claude.ai/code/session_01PNenigaFnwvcTs2kDND16Z

Co-authored-by: turtle hermit <4173875+cte13@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ttymin#360)

Status lines that describe an unchanged state are written on every poll:
uninstalled providers (phase1 recv ... today=nil), a missing Cursor login
(cursor api / cursor limits: no session token), a missing codex binary,
unavailable limits, provider status, the Claude desktop scan depth notice.
AppLog rotates at 2 MB, so this noise pushes out the history needed to
diagnose a crash or a failed sync. In one real log, `cursor api: no session
token — state.vscdb: missing` alone appeared 2,658 times in three weeks.

Add LogRepeatSuppressor (pure, tested) and AppLog.writeIfChanged, and use it
for those 11 status lines. A changed message is written at once, an
unchanged one is reaffirmed once an hour, and each provider/state has its own
key so one change never lifts another's suppression. Event lines and lines
whose values change every tick keep using AppLog.write.


Claude-Session: https://claude.ai/code/session_01PNenigaFnwvcTs2kDND16Z

Co-authored-by: turtle hermit <4173875+cte13@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…cache (chattymin#359)

LocalUsageProvider uses LocalUsageCache.shared, whose default path is the
user's real Application Support/PokeTokenBar/usage-cache.json with no gate.
Any refresh() that runs the real providers during `swift test` (a store built
with default providers, a detached Task that outlives its test) reads and
rewrites that file. Measured with the app quit: one full suite run changed
the real file's checksum and grew it from 965 KB to 1.02 MB; no other state
file changed.

Tests cannot catch this by injecting paths: injection covers the test that
does it, not the process, so the default-path branch is invisible to them.
The gate belongs in the store. AppEnv.persistsToUserLocation owns the rule
(an injected path is always live; the default path only in the app bundle)
so later stores reuse it instead of copying it. Reads are gated as well as
writes, so a test cannot assert on fixtures mixed with the user's data.

Verified by fault injection: removing the read gate loads 156 real blobs
next to the single fixture; removing the write gate changes the real file's
mtime. With the fix, a full run leaves every state file unchanged.


Claude-Session: https://claude.ai/code/session_01PNenigaFnwvcTs2kDND16Z

Co-authored-by: turtle hermit <4173875+cte13@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ymin#357)

The menu bar limit items now use the same color as their popover gauge:
the six pace tiers when the window's pace is known (Claude 5-hour, the
Codex primary window), the warn/crit threshold colors otherwise
(Antigravity, Cursor). Only each "Label NN%" item is colored; separators
and the token/cost line keep the system text color.

- PaceTier.gauge is the single rule; the popover's quotaRow uses it too,
  so the two surfaces cannot disagree.
- Settings > Menu bar > Percent color (shown with Limit %): Match gauges
  (default), Only when fast, or Off.
- The colors are recomputed on every refresh, so a tier that moves with
  the clock redraws even when the text stays the same. No new timer.
…in#360, chattymin#361)

Merges upstream/main (merge commit, not squash, to keep ancestry):
- chattymin#357 feat(menubar): color limit percentages like the popover gauges
- chattymin#359 fix: stop the test suite from reading and rewriting the user's usage cache
- chattymin#360 fix: stop unchanged status lines from filling the log every poll
- chattymin#361 fix: log each unpriced model once so a missing price row is noticed

chattymin#359/chattymin#360 are the upstreamed forms of the fork's #22/#29, so conflicts
were the same code with reworded (English) comments; upstream's side was
taken. Fork-only changes kept:
- Antigravity manual-refresh failure message (UsageStore catch block)
- OpenCode Go menu bar item, ported to chattymin#357's MenuLimitPart via add()
- dropped the fork's duplicate persistsToDisk assignment that auto-merge
  kept next to upstream's

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PNenigaFnwvcTs2kDND16Z
@cte13
cte13 merged commit ce251c7 into main Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants