Skip to content

Latest commit

 

History

History
104 lines (93 loc) · 6.25 KB

File metadata and controls

104 lines (93 loc) · 6.25 KB

Project agent memory

This file is the project's committed home for project-intrinsic agent knowledge: build, test, release, architecture, and sharp-edge notes that should travel with the code.

The app ships as Left (products Left and LeftCore); it was UsageBar until 2026-07-28. docs/naming.md records the rename and what it deliberately left alone.

  • Run ./scripts/verify.sh for the full local gate: tests, release build, temporary app packaging/signing, the UsageBarLeft migration, bundle and icon checks, fixture rendering against committed evidence, secret scan, and diff checks.
  • Run ./scripts/install.sh to install and launch the menu-only app in ~/Applications; use --destination and --no-launch for a scoped install.
  • Keep provider access behind QuotaProviderAdapter in Sources/LeftCore/QuotaAXIAdapter.swift. Production invocation arguments are fixed to --provider claude,codex,grok --json and must never include --full or credentials. The child gets an allowlisted environment and a streaming 1 MiB stdout cap.
  • Fixtures/demo-quota.json is a synthetic, credential-free source for tests, local demo mode, docs/demo.png, and docs/menubar.png. It is invented, not a live dump. Re-render both whenever the surface changes (--render-preview, --render-status) — verify.sh compares them byte for byte.

Presentation invariants

These are held by tests, not by taste. Change the constant, not the view.

  • CFBundleIdentifier stays local.firstmate.usagebar through any rename. macOS keys login-item registration, TCC decisions, and keychain ACLs to it, so a new identifier is a new app with none of the grants. AppIdentity states this; AppIdentityTests holds it.
  • Sources/LeftCore/PopoverLayout.swift is the single source for the 340 pt grid. The surface has no fixed height: surfaceHeight(...) sums header, sections, and footer, AppDelegate resizes the popover from it, and PreviewRenderer renders at it — so a void under the last row is a test failure, not a screenshot review. PopoverLayoutTests also holds the worst case (three provider notices plus a global banner) under maximumHeight, since nothing scrolls.
  • The status item is one template image, composed by Sources/LeftCore/MenuBarStatus.swift: provider glyphs and digits both live in its alpha channel, so macOS owns the light, dark, and highlighted appearances. Attributed-title attachments do not get template treatment — that is why this is drawn rather than typeset. VoiceOver gets accessibilityLabel(for:), the only spoken form of the readout.
  • Sources/LeftCore/LeftMark.swift draws the app's own open ring, and the installer renders the .icns by running the built binary — Left's identity is still code, with no checked-in image asset.
  • The provider marks are Anthropic's, OpenAI's, and xAI's official logos, not ours to redesign. Assets/provider-marks/ holds the SVGs retrieved from claude.ai, chatgpt.com, and grok.com; scripts/vendor-provider-marks.py derives the generated ProviderMarkGeometry.swift from them; SVGPath.swift parses it and ProviderMark.swift fills it. ProviderMark.forProvider is an explicit three-way map (claude / codex / grok); unknown ids are nil, never GPT. Read docs/provider-marks.md before touching any of it — it records the sources, hashes, refresh procedure, and the trademark position. ProviderMarkProvenanceTests fails if the asset bytes, the compiled geometry, or the rendered silhouette drift, and explicitly if the retired cloud or hex ring comes back. Grok's favicon is a plated Figma export: the compiled geometry is the two unmasked #FCFCFC blades, not the rounded square. The one carve-out to "colour is state, never data" lives here: the overlay draws each mark in its vendor's own colour, held to WCAG 3:1 by PaletteTests; the menu bar stays a monochrome template because macOS owns a status item's appearance.
  • Sources/LeftCore/PlainLanguage.swift owns every sentence the app says about a provider state. Upstream quota-axi reasons are translated where we recognise them and dropped where we do not; the raw string reaches only the row tooltip via ProviderPresentation.technicalDetail. PlainLanguageTests bans implementation words and caps message length at the one line the notice actually has.
  • Colour is state, never data: rails are monochrome until a window crosses the watch (25%) or critical (10%) threshold. PaletteTests holds every text colour to WCAG AA against the surface it is drawn on.

Diagnosing "stale" readings

A stale provider is almost always an upstream condition, not an app bug — the app only translates what quota-axi reports. Diagnose upstream first; the provider's own wording is in the notice row's tooltip.

  • quota-axi consumes provider access tokens only; it never exercises a refresh token. Claude therefore goes stale roughly 8h after the last Claude Code activity on that machine, and quota-axi reports it as state.error: "Claude sign-in required" even while claude auth status still says loggedIn: true. Any Claude Code request refreshes the token and restores freshness — that is the remedy, not re-authentication. Codex is immune because its provider delegates to the codex CLI over cli-rpc, which refreshes itself.
  • quota-axi auth --json reports per-source credential status without printing secrets. Over SSH the keychain source always reports keychain_access_denied (security cannot prompt outside the GUI session), so an SSH run is not a faithful reproduction of what the installed app sees.
  • ~/.cache/quota-axi/quotas.json stores only fresh snapshots, so a provider's state.refreshedAt there is its last good fetch, and the file's top-level generatedAt is merely the last write. Comparing the two dates attributes staleness without touching credentials.

Maintaining this file

Keep this file for knowledge useful to almost every future agent session in this project. Do not repeat what the codebase already shows; point to the authoritative file or command instead. Prefer rewriting or pruning existing entries over appending new ones. When updating this file, preserve this bar for all agents and keep entries concise.