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.shfor the full local gate: tests, release build, temporary app packaging/signing, theUsageBar→Leftmigration, bundle and icon checks, fixture rendering against committed evidence, secret scan, and diff checks. - Run
./scripts/install.shto install and launch the menu-only app in~/Applications; use--destinationand--no-launchfor a scoped install. - Keep provider access behind
QuotaProviderAdapterinSources/LeftCore/QuotaAXIAdapter.swift. Production invocation arguments are fixed to--provider claude,codex,grok --jsonand must never include--fullor credentials. The child gets an allowlisted environment and a streaming 1 MiB stdout cap. Fixtures/demo-quota.jsonis a synthetic, credential-free source for tests, local demo mode,docs/demo.png, anddocs/menubar.png. It is invented, not a live dump. Re-render both whenever the surface changes (--render-preview,--render-status) —verify.shcompares them byte for byte.
These are held by tests, not by taste. Change the constant, not the view.
CFBundleIdentifierstayslocal.firstmate.usagebarthrough 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.AppIdentitystates this;AppIdentityTestsholds it.Sources/LeftCore/PopoverLayout.swiftis the single source for the 340 pt grid. The surface has no fixed height:surfaceHeight(...)sums header, sections, and footer,AppDelegateresizes the popover from it, andPreviewRendererrenders at it — so a void under the last row is a test failure, not a screenshot review.PopoverLayoutTestsalso holds the worst case (three provider notices plus a global banner) undermaximumHeight, 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 getsaccessibilityLabel(for:), the only spoken form of the readout. Sources/LeftCore/LeftMark.swiftdraws the app's own open ring, and the installer renders the.icnsby 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.pyderives the generatedProviderMarkGeometry.swiftfrom them;SVGPath.swiftparses it andProviderMark.swiftfills it.ProviderMark.forProvideris an explicit three-way map (claude/codex/grok); unknown ids arenil, never GPT. Readdocs/provider-marks.mdbefore touching any of it — it records the sources, hashes, refresh procedure, and the trademark position.ProviderMarkProvenanceTestsfails 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#FCFCFCblades, 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 byPaletteTests; the menu bar stays a monochrome template because macOS owns a status item's appearance. Sources/LeftCore/PlainLanguage.swiftowns every sentence the app says about a provider state. Upstreamquota-axireasons are translated where we recognise them and dropped where we do not; the raw string reaches only the row tooltip viaProviderPresentation.technicalDetail.PlainLanguageTestsbans 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.
PaletteTestsholds every text colour to WCAG AA against the surface it is drawn on.
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-axiconsumes 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, andquota-axireports it asstate.error: "Claude sign-in required"even whileclaude auth statusstill saysloggedIn: 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 thecodexCLI overcli-rpc, which refreshes itself.quota-axi auth --jsonreports per-source credential status without printing secrets. Over SSH the keychain source always reportskeychain_access_denied(securitycannot prompt outside the GUI session), so an SSH run is not a faithful reproduction of what the installed app sees.~/.cache/quota-axi/quotas.jsonstores only fresh snapshots, so a provider'sstate.refreshedAtthere is its last good fetch, and the file's top-levelgeneratedAtis merely the last write. Comparing the two dates attributes staleness without touching credentials.
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.