Skip to content

Editorial redesign: bilingual menu-bar app with stats, floating HUD & live quota - #1

Merged
TeaLance merged 14 commits into
mainfrom
feature/editorial-redesign
Jun 20, 2026
Merged

Editorial redesign: bilingual menu-bar app with stats, floating HUD & live quota#1
TeaLance merged 14 commits into
mainfrom
feature/editorial-redesign

Conversation

@TeaLance

Copy link
Copy Markdown
Owner

A full redesign of AgentMeter into a polished, deliberately distinct editorial-minimal menu-bar app (paper/ink, hairlines, tabular numbers) — not a cc-bar look-alike. 14 commits across Phases 0–7 and Rounds 2–4.

Highlights

  • Editorial design system — paper/ink palette, hairline dividers (no cards), tabular numbers; identity color (per-service, customizable) strictly separated from status color (4-state by remaining quota).
  • Bilingual (繁中 / English) — manual, instant toggle via a runtime tr() store (no resource bundle, no restart).
  • Dropdown panel — hero number + thin bars + per-service footer; 5h|週 hero toggle.
  • Stats window — sidebar (All/Claude/Codex · overview/by-model/timeline), time-range tabs, daily bar chart, by-model table. Unified single window with the settings tabs.
  • Floating desktop HUD — minimal dual rings, draggable, edge-snapping, idle-dimming.
  • Local $ estimate — token counts × built-in price table (ModelIdentity + Pricing); plain $X.XX, when unpriced.
  • Live quota — Claude via the statusLine bridge (local, no network); Codex 5h/weekly via the verified wham/usage endpoint.
  • Accounts — read from local credential files only (never the Keychain).

Menu bar (Round 4)

  • Per-service agent logo drawn as a monochrome template glyph (adapts to light/dark).
  • Vertical (stacked) / horizontal (inline) layout + a "show agent icon" toggle.
  • Codex 5h / weekly added as menu-bar metrics; CC/CX text prefix dropped (the logo identifies the tool).

Privacy

  • Offline by default. All networking lives in Sources/AgentMeter/Network/, enforced by Scripts/check-offline.sh + a CI workflow.
  • Account display reads files only; no Keychain access anywhere.

Verification

  • 66 Core unit tests pass (pricing, model identity, history/Codex state machine, credentials).
  • ./Scripts/check-offline.sh passes — no networking outside Network/.
  • Core stays AppKit/SwiftUI/URLSession-free.

TeaLance and others added 14 commits June 20, 2026 01:00
…, new panel

Pivot the UI away from the cc-bar-style cards/rings to an editorial-minimal
language (paper/ink, hairlines, tabular numbers, thin bars, no cards).

Core (TDD):
- StatusLevel: 4-state status (normal/warning/low/empty) by remaining quota.
- HexColor: #RRGGBB parse/format.

UI foundations:
- DesignSystem: editorial tokens (light+dark), single statusColor source,
  adaptive Color(amLight:dark:) + Color(amHex:).
- Components: Hairline, ServiceSwatch, ThinBar, MetricRow, HeroNumber,
  SegmentedPair ([5h|wk]).
- LanguageStore + tr(en,zh): live manual zh/en switch, no restart.
- ServiceColorStore: per-service identity colour (brand/mono presets).

Panel rebuild (MenuContentView):
- Hero number per service (Claude 5h default, [5h|wk] toggle to weekly;
  Codex shows context%), aligned mini-rows, today tokens + messages footer.
- Status colour drives values/bars; identity colour drives the swatch only.

42 Core tests pass; app builds and launches. Cost ($) deferred to pricing phase.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… panel

Core (TDD):
- ModelKey: normalize model ids (strip dated -YYYYMMDD and [1m] suffixes,
  <synthetic>/empty -> unknown), vendor classification.
- Pricing: ModelPricing (per-MTok, cache 0.1x/1.25x, reasoning=output),
  CostEstimate (amountUSD + isComplete), built-in Anthropic list prices,
  costEstimate(tokens,model) and costEstimate(byModel:). OpenAI/Codex models
  intentionally unpriced -> isComplete=false (renders "≈$X+").
- ToolUsage gains todayByModel; ClaudeCodeReader buckets today's tokens by
  normalized model key (parity test: buckets sum to flat today).

UI:
- Panel footer shows ≈$ from costEstimate(byModel:) when per-model data exists
  (Claude); hidden for Codex (no per-model attribution / no OpenAI pricing yet).

55 Core tests pass; app builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…machine

Core (TDD):
- UsageHistory/DayBucket types + HistoryAccumulator (shared bucketing).
- ClaudeCodeReader.history(range:): per-day, per-model buckets, dedup by
  (id,requestId), range-bounded; independent of read(now:) for safety.
- CodexReader.history(range:): turn_context -> token_count model state machine
  (model attributed per-file, reset per file, .unknown before any turn_context);
  added payload.model decoding.

Tests: Claude day/model bucketing + dedup + range bounds; Codex two-models-in-one-file
attribution, no-leading-turn_context -> unknown, per-file state reset. 59 Core tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cker)

- SettingsView rebuilt as a localized TabView: General (language 繁中/English,
  refresh interval, Claude hero metric 5h/weekly, launch-at-login), Appearance
  (per-service ColorPicker + brand/mono preset swatches, pricing-version note),
  Menu Bar (metrics multi-select + show toggles), Claude Quota (statusLine bridge).
- Migrated hardcoded strings to tr(en,zh): MenuBarMetric titles, refresh-interval
  labels, all settings strings. Live language switch re-renders via LanguageStore.
- ServiceColors: hexString(from: Color) pins sRGB so ColorPicker round-trips don't drift.

App builds; bilingual + custom colors now fully operable from Settings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-model)

- StatsWindowController: AppKit NSWindow hosting SwiftUI; opens from .accessory
  via ActivationPolicyCoordinator (ref-counted .regular↔.accessory, resets on
  app deactivation — replaces the old willResignActive revert).
- StatsViewModel: runs ClaudeCodeReader/CodexReader.history(range:) off-main per
  time range (Today/7d/30d/All), publishes UsageHistory.
- StatsRootView (editorial): custom sidebar (service filter + Overview/By-model),
  range tabs, KPIs (total tokens, ≈total cost, per-service), Swift Charts stacked
  daily bar chart (per-service identity colors), by-model table with ≈$ per model.
- Panel header gains a chart.bar button to open it.

App builds and launches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FloatingPanel: NSPanel (.nonactivating/.borderless, .floating level, all-Spaces,
  movable-by-background) that snaps to the nearest screen edge on mouse-up.
- FloatingHUDView: dual thin-line RingMeters — frame tinted with the identity
  colour, arc in the status colour, mono % centre; translucent material; dims to
  the configured idle opacity and brightens on hover. Live from UsageStore.shared.
- FloatingPanelController: show/hide driven by the floatingEnabled default;
  refresh() called at launch to restore, and from the Settings toggle.
- RingMeter component gains a configurable track colour.
- Stores exposed as shared singletons so the HUD/launch get the live instances.
- Settings gains a Floating tab (enable, per-service, idle-opacity slider).

App builds and launches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uarantee

- NetworkFeature (codexQuota, accurateCost): off by default; localized title +
  "this needs internet" explanation; isEnabled reads the defaults flag.
- Advanced settings tab: each feature toggle asks for confirmation before
  enabling (alert with the explanation); offline-by-default statement.
- CodexQuotaClient / BillingClient: consent-gated skeletons confined to
  Sources/AgentMeter/Network/. EXPERIMENTAL — return .unavailable rather than
  fabricate data; real OpenAI/billing endpoints intentionally not wired (unverified).
- Scripts/check-offline.sh + offline-check CI workflow: fail if any networking
  (URLSession/Network/...) appears outside Network/ — keeps the guarantee verifiable.

59 Core tests pass; app builds and launches; offline check passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- R2.1 Cost: shared moneyString — plain "$X.XX", no ≈/+; "—" when a service/total
  is entirely unpriced (no misleading $0.00). Stats KPI label "≈ 總花費" → "總花費".
- R2.2 Launch-at-login: detect unbundled/dev runs (not an installed .app) and
  disable the toggle with a clear note instead of the raw "Invalid argument" error.
- R2.3 Merge Stats + Settings into one window with cc-bar-style top tabs
  (用量統計 | 設定): new MainWindowRootView + MainWindowModel; both the chart and
  gear buttons open it to the right tab; removed the separate SwiftUI Settings scene.

App builds and launches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… endpoints)

Core (TDD, file-only — never the Keychain):
- Credentials.swift: JWT.payload (base64url), ClaudeCredentialParser
  (.credentials.json + .claude.json email fallback), CodexCredentialParser
  (auth.json + id_token JWT for email/plan), ServiceAccount, CredentialReader.
  +6 tests (65 total).

Network (Network/, opt-in, gated):
- CodexQuotaClient: GET chatgpt.com/backend-api/wham/usage (Bearer + optional
  ChatGPT-Account-Id), defensive window parsing, .unavailable on mismatch.
- CodexTokenRefresher: auth.openai.com/oauth/token refresh + write-back to auth.json.
- NetworkFeature: codexQuota (network) + showAccounts (reads credential files,
  not network); dropped speculative accurateCost + BillingClient.

Wiring:
- UsageStore: codexFiveHour/codexWeekly + claudeAccount/codexAccount, populated
  on refresh when the matching opt-in is on.
- Panel: service headers show plan (e.g. "Codex · Plus"); Codex hero uses real
  5h when present, else context% + an "enable live quota" link.
- Settings Advanced: showAccounts ("reads credentials") + codexQuota ("needs
  internet") toggles with confirmation; refresh on toggle.

Privacy: README updated to "offline by default + opt-in"; "never reads Keychain"
still holds (files only). offline check still green; 65 tests pass; app launches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ining/used

- R3.8 Token counts now use total (incl cache reads) to match cc-bar; the ~36x
  gap was billable-vs-total. Cost unchanged (already weights cache reads).
- R3.1 One window, one tab bar: 用量統計 is now the first tab alongside the
  settings panes (RootTabView + AppTab); dropped the separate pill switcher.
- R3.5/R3.7 Accounts section in General: Claude/Codex email · plan · connected,
  so enabled accounts actually show.
- R3.6 Default ON: codexQuota + showAccounts default enabled (NetworkFeature
  treats absent key as true) for accurate quota/accounts; README updated (still
  never reads Keychain; off switch in Advanced).
- R3.4 Removed the offline-explainer captions in Advanced.
- R3.3 Menu bar renders each metric as a stacked label/value (Stats-app style).
- R3.2 New "Meters show used/remaining" setting, applied to panel + floating HUD.

65 tests pass; offline check green; app builds and launches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eychain

The real macOS setup keeps the Claude token in the Keychain (which we never
read), so ~/.claude/.credentials.json is absent and the account showed as "not
detected". ~/.claude.json still has oauthAccount (email + organizationType), so:

- ClaudeCredentialParser.account(fromClaudeConfigJSON:) derives email + plan
  (organizationType "claude_team" → "team").
- CredentialReader.claude() no longer requires .credentials.json — it returns an
  account from ~/.claude.json alone (token optional; Claude quota uses the bridge).
  Still file-only, never the Keychain.

66 Core tests pass; offline check green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Menu bar is ~22pt tall; 8/10pt stacked lines clipped the value. Shrink to
  7/9pt with -1.5 spacing + fixedSize so both lines fit.
- MenuBarMetric.parts now honors meterShowsRemaining (5h/weekly/context show
  remaining when set); MenuBarLabel re-renders on the setting via @AppStorage.

App builds and launches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MenuBarExtra clips multi-line SwiftUI labels to the bar height, so the value
line never showed. Render each metric as a 2-line (label/value) template NSImage
at natural size; the menu bar scales it to fit and tints it for light/dark.

App builds and launches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Menu bar
- Draw each service's logo before its metrics, as a monochrome template
  glyph (new MenuBarIconPath.swift: embedded Claude/Codex SVG paths + a
  minimal absolute-command path parser → NSBezierPath). Adapts to
  light/dark like the text.
- Add vertical (stacked) / horizontal (inline) layout option, and a
  "show agent icon" toggle. Horizontal uses one uniform font size with a
  shared baseline; logo sized to match the text height.
- Add Codex 5h / weekly as menu-bar metrics (data already fetched via the
  Codex quota endpoint).
- Drop the CC/CX text prefix — the per-service logo identifies the tool;
  only the combined total keeps a "Σ" marker.

Settings
- Consolidate connectivity onto General: a single "Live quota &
  connectivity" section with show-accounts, Claude live quota (statusLine
  bridge, local) and Codex live quota (needs internet). Remove the now
  redundant Advanced and Claude-Quota tabs.
- Split menu-bar metrics into Claude / Codex / Combined sections.
- Remove the Claude hero-metric picker (the panel's 5h|週 toggle owns it)
  and several explanatory captions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@TeaLance
TeaLance merged commit f842a7c into main Jun 20, 2026
4 checks passed
@TeaLance
TeaLance deleted the feature/editorial-redesign branch June 20, 2026 15:34
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.

1 participant