Visual system for the tray flyout and taskbar-adjacent capacity strip (SOU-127).
- Theme: follow Windows light/dark (
auto). Light mica is the design baseline; dark is a paired token set. - Material: CSS mica/glass (
backdrop-filter+ translucent tint). Native DWM mica is out of scope. - Type: Windows-first —
Segoe UI Variable,Segoe UI, system-ui. - Accent: Ceiling cyan (
#26b5cefamily) on slate neutrals. No purple dashboard, no cream/terracotta, no glow effects. - Focus: a solid outline or a zero-blur ring. A blurred
box-shadowaround a focused control is a glow and is not allowed;0 0 0 Npxrings are. - Brand: user-visible chrome says Ceiling. Internal
codexbarcrate IDs stay unchanged.
The webview should have no network reach of its own. Every provider call runs in
Rust, so the target release connect-src is 'self' ipc: http://ipc.localhost
and nothing else.
Not yet true. apps/desktop-tauri/src-tauri/tauri.conf.json is the single CSP
for every build, and it still allows http://localhost:*, http://127.0.0.1:*,
ws://localhost:* and ws://127.0.0.1:* so the Vite dev server and its HMR
socket can connect. Confining those loopback origins to dev builds is SBS-819;
read the config, not this section, for what ships today.
| Token | Light | Dark | Role |
|---|---|---|---|
--ceiling-accent |
#1a9bb5 |
#26b5ce |
Brand / focus |
--ceiling-glass-bg |
rgba(245, 247, 250, 0.78) |
rgba(28, 32, 38, 0.72) |
Panel fill |
--ceiling-glass-border |
rgba(15, 23, 42, 0.08) |
rgba(255, 255, 255, 0.08) |
Hairline edge |
--ceiling-glass-blur |
18px |
18px |
Backdrop blur |
--text-primary |
#0f172a |
#f1f5f9 |
Primary copy |
--text-secondary |
#64748b |
#94a3b8 |
Meta / freshness |
--state-stale |
#b45309 |
#f59e0b |
Cached / stale |
--state-error |
#dc2626 |
#f87171 |
Failed read |
--state-lifted |
#0f766e |
#2dd4bf |
Not enforced |
--radius-panel |
12px |
12px |
Flyout / cards |
--radius-pill |
8px |
8px |
Strip pills |
Usage bars keep calm slate→cyan progression; warn/crit stay amber/red without neon glow.
┌─────────────────────────────┐
│ [icon] 42% left [stale] │
│ 5-hour │ ← constraining window label (quiet)
└─────────────────────────────┘
- One glance: provider mark + remaining/used %. Cursor headlines its account-wide Total meter; other providers use the constraining window.
- State chip only when not live:
stale|error. - Live pills omit the chip.
- Optional reset countdown stays secondary, not a second percentage.
┌──────────────────────────────────────────────┐
│ [logo] Cursor Pro │
│ 62% left · Monthly resets 12d │
│ ████████░░ │
│ Auto 90% ████████████░░ (if hot) │
└──────────────────────────────────────────────┘
- Overview cards lead with primary plan pool (not Auto alone).
- Most providers show at most one companion lane, and only when hot (≥ ~70% used, or hotter than the pool).
- Three providers pin their companion lanes and show them at any percentage,
because a hidden lane there would misstate what actually caps the plan
(
PINNED_COMPANION_IDSinlib/capacityPresentation.ts):- Cursor — Auto and API are both account-wide and always meaningful; the On-demand lane joins them only while it is actually active.
- Claude — Weekly sits beside the 5-hour session and both cap the subscription.
- OpenCode Go — bills against rolling, weekly, and monthly ceilings at once, so hiding weekly leaves a gap between the two that do show.
- Clicking a card opens detail — it does not toggle which meter is shown.
- Provider switcher uses brand-tinted icons + status dots; bar = constraining %.
┌──────────────────────────────────────┐
│ [logo] Cursor you@… Pro │
│ web · updated 2m ago │
│──────────────────────────────────────│
│ Monthly ████████░░ 62% 12d │
│ Auto ████░░░░░░ 38% 12d │
│ API ██░░░░░░░░ 12% 12d │
│ Promotional ░░░░░░░░░░ 0% 12d │
│ 5-hour · Not currently enforced │ ← inactive text row
│──────────────────────────────────────│
│ token history / limits (detail only) │
└──────────────────────────────────────┘
Detail lists every measured window plus inactive rows. Do not truncate to two metrics.
| State | Strip | Flyout |
|---|---|---|
| Live | Normal pill, no chip | Quiet freshness text |
| Cached / stale | Muted pill + stale chip |
Dim metrics + stale label |
| Error | Crit outline + error chip |
Error line, no fake bars |
| Not enforced | No chip from inactive windows; the active window headlines and normal/stale/error freshness still controls the pill | Quiet named text row via inactiveRateWindows (state: "notEnforced") |
| Unavailable | Same quiet treatment, in a caution (amber) tone; the window was tracked but dropped out of a successful response | Named row via inactiveRateWindows (state: "unavailable"), labeled "Unavailable" |
inactiveRateWindows[].state carries the explicit enforcement state: notEnforced (provider reported no active limit) vs unavailable (a first-class provider's window vanished from an otherwise-successful response, detected by the cross-snapshot enforcement tracker). Never invent 0% or 100% for either — surface the named state instead.
Temporary provider-reported promotions use promoSignals:
| Kind | Strip | Overview | Detail |
|---|---|---|---|
boost |
Hidden (named in the pill tooltip only) | Hidden | Accent chip |
inclusion |
Hidden | Hidden | Neutral chip |
The strip draws no promo chrome at all — FloatBar.tsx puts the boost title in
the pill's title text and nothing else. "keeps promo signals out of the strip
chrome" in FloatBar.test.tsx pins both halves: the absent chip and pill
classes, and the title that must still carry it. The strip is persistent desktop
furniture; a temporary promotion is not worth a permanent mark on it.
The detail chip's accent treatment is a border plus a 1px ring, never a halo: the no-glow rule above has no exceptions, promos included.
Account identity is also hidden from overview cards. Provider settings and the Accounts section remain the intentional places for email/source details.
Never invent promos from marketing copy. Claude omelette / Cursor bonus pools only.
The floating bar is persistent desktop furniture, so its resting state is static. Do not add ambient shimmer, breathing, glow loops, or continuously moving gradients.
- Hover: a one-pixel lift and slightly clearer glass edge.
- Scheduled reset: one calm cyan sweep, then briefly show the replenished value.
- Confirmed surprise reset or restored capacity: a slightly brighter cyan sweep plus a short success halo around only the affected provider segment.
- Capacity loss or error: one restrained amber/red edge pulse; never loop.
- Windows toast and bar animation come from the same confirmed capacity event.
- Confirmed events cover scheduled and surprise resets, significant reset-time moves, lifted/restored windows, and newly reported extra allowance.
- Cursor On-demand is billed spend. It never emits capacity events or OS alerts. Do not invent a Promotional meter — Cursor does not report a grant to meter bonus consumption against.
- The dedicated Reset and Capacity Alerts preference controls OS alerts; turning it off does not disable the in-bar event treatment.
- Respect the global animation preference and
prefers-reduced-motion.
Reset animation must consume the authoritative capacity-event observer output from SOU-125. Do not infer resets independently in React from a single percentage drop. Legacy threshold and session-transition alerts likewise require two consecutive provider readings before notifying.
Three parts of a form field are painted by the engine, not by our CSS: the number-input spinner arrows, the text caret, and the selection highlight.
-
Spinner arrows and caret are pinned on the controls themselves —
color-schemeandcaret-coloron.select,.number-input,.text-input, andtextarea, not only on the root element.color-schemeis what the engine draws the arrows from, and the app theme can differ from the Windows theme, so inheriting it risks a light spinner on a dark field. -
Selection is one
::selectionrule with no scope, on purpose, so a selected run looks the same in a form field, in About copy, in a log, in provider detail, and in the click-to-select setup command block. It is not split per control and not split per[data-theme]; only the--accentand--selection-inktokens it reads are per theme. The same rule also namesinput/textareaexplicitly, because their text lives in a form-control shadow tree a document rule may not reach, and it sets-webkit-text-fill-coloras well ascolor, because Blink paints form-control glyphs through the former. -
Under
forced-colors: active(Windows Contrast Themes) both selection and caret hand back toHighlight/HighlightTextandCanvasText. Highlight pseudos are not reliably force-adjusted, so an app painting its own selection can otherwise stay cyan on a yellow-on-black page. -
Selection is a solid
background-color, never a translucent wash and never acolor-mix(). Opaque is what makes one unscoped rule safe: the ink sits on the fill, not on whatever surface is underneath, so the reading is the same everywhere. It also leaves nothing for an older WebView2 to drop — acolor-mix()it cannot parse would take the whole declaration with it. Two numbers have to hold, the fill against the unselected field and the ink against the fill:- Dark — fill
#26b5ceon the field (rgba(255, 255, 255, 0.04)over#1c2026, so#25292f) is 5.98:1; ink#0f172aon the fill is 7.30:1. - Light — fill
#0b8197on the#fffffffield is 4.56:1; ink#ffffffon the fill is the same 4.56:1.
An earlier draft washed the accent to 38% and reused
--text-primaryas the ink: about 1.7:1 over a white field with no glyph change at all, which is invisible in a 52px number field. The glyph colour now changes in both themes. - Dark — fill
-
Dropdowns, checkboxes, sliders, and scrollbars already carry per-theme styling.
Once color-scheme is pinned, the engine-drawn parts follow the app theme.
The Windows theme still needs checking because it drives surrounding chrome and
because a regression here shows up as the two disagreeing.
Light and dark, with at least one live Codex/Cursor account:
- Tray overview showing multiple windows + one inactive row.
- Tray provider detail with token history/limits if available.
- Capacity strip with a live pill and a stale/error pill.
- Settings / About showing Ceiling branding.
- Settings form controls — dropdown, checkbox, number input with its spinner,
slider, scrollbar, focus ring, and selected text inside a
.text-inputand a.number-input. Capture these on a light-themed and a dark-themed Windows for each app theme. The two are independent settings, and this pass is what catches the engine-drawn parts following the wrong one.