feat(opencode): colored, collapsible OpenCode 2 sidebar with real quota freshness

TO
11h ago
+544-43

Why

On OpenCode 2 the Antigravity sidebar is hard to read and partly wrong:

  • every row is plain text, although projectSidebar() already computes a tone per quota row and per status;
  • the footer always says "quota not checked yet", because both GA state sources hard-code status.checkedAt: null;
  • vault pools always read "no quota reading yet", because the vault state source drops each row's cachedQuotaUpdatedAt.

OpenCode 1 users get colored bars and a collapsible header; GA users get neither.

What changes

State freshness (plugin/ga-location-services.ts)

  • state answers report the newest cached quota reading as status.checkedAt (local and vault), or null when no row holds a reading. A checkedAt the location reports itself still wins.
  • Vault rows forward cachedQuotaUpdatedAt, so vault pools show their figure and age like local pools.
  • No protocol change: checkedAt was already a nullable number.

GA sidebar (ga/tui/host-ga.tsx)

  • Reads optional theme tokens from the host context (text.base, text.muted, text.feedback.{success,warning,error}.base, text.action.primary.base, background.base) and colors figures, bars and status by the existing projection tones; secondary text is muted, degraded-state notices use the warning color.
  • Each pool with a reading draws a remaining-quota bar sized from the measured sidebar width (up to 10 cells, dropped below 4), so narrow sidebars keep today's exact text.
  • The header is a clickable ▼/▶ ANTIGRAVITY badge; collapsed, each account takes one line (label, lowest remaining figure, status dot). The choice persists through context.storage.store when the host provides it.
  • Without a theme or storage the sidebar renders the same text uncolored and collapse lasts for the session.
 ▼ ANTIGRAVITY
Account 1                         active
Gm    ██████░░░░ 55% left
NG    no quota reading yet
  health 90
Route Account 1 · gemini · antigravity

The GA module graph is unchanged (still pinned by tui.test.tsx); @opentui/core is used as a type-only import.

Out of scope

  • quotaBackoffUntil in GA stays null.
  • Tone thresholds are unchanged (remainingTone: ≤30 warn, ≤10 err).
  • The notification/apply wire shapes between host-ga.tsx and ga/server (see #40).

Tests

  • ga-location-services.test.ts: newest-reading helper; local checkedAt from rows; reported checkedAt kept; vault rows forward reading times and report the newest; no identity in the answer.
  • tui.test.tsx: colors by tone with a fake theme (span colors), bar sizing and fill, collapsed figure, header click collapses and saves to storage, starts collapsed from storage; the existing width-32 reset-span frame is unchanged; one frame expectation updated for the new bar.
  • Gates run locally with Bun 1.4.2: build, typecheck, lint, format:check. bun test --isolate ./src ./scripts/build-tui.test.ts in packages/opencode: the 36 failures are the same on main without this change in this environment (Pi native path, setup CLI, GA test-case lifetime), none in touched areas. Existing mutations.toml anchors in touched files still match exactly once.

Specified with OpenSpec (proposal, two capability specs ga-sidebar and ga-state-status, design, tasks); the artifacts are kept out of the repository.


Summary by cubic

Makes the OpenCode 2 Antigravity sidebar readable and accurate: quota rows, bars, and status are now colored with the host theme, and state answers report the newest cached quota reading so the sidebar stops claiming "quota not checked yet."

State freshness

  • state answers report the newest cached quota reading as status.checkedAt for local and vault accounts, or null when no reading exists; a reported checkedAt still wins.
  • Vault pools forward cachedQuotaUpdatedAt, so they show their figures and ages instead of "no quota reading yet."

Sidebar UI

  • The header is a clickable ▼/▶ ANTIGRAVITY badge that collapses the panel to one line per account (label, lowest remaining figure, status dot); the choice persists in host plugin storage when available.
  • Quota rows draw a remaining-quota bar sized from the measured sidebar width, dropped when it does not fit; tones color figures, bars, and status, secondary text is muted, and degraded-state notices use the warning color.
  • Without a theme or storage, the sidebar renders the same uncolored text and collapses for the session only.