Skip to content

Ceiling 1.5.33

Choose a tag to compare

@github-actions github-actions released this 16 Aug 21:41
· 89 commits to main since this release
81c096a

[Ceiling] 1.5.33 - 2026-08-16

Adds an activity heatmap to Charts and an opt-in spend warning that needs no budget set, and lets the floating bar follow whichever app you are working in. Mostly, though, this release stops surfaces reporting a state they could not actually read: a provider outage is now told apart from an empty quota, a missing Cursor plan reads as unavailable rather than 0% used, SuperGrok's weekly figure is decoded rather than guessed at, the taskbar strip stops cutting the last character off a reset, and prices, chart caches, and CLI logs stop losing or hoarding data on disk. Release builds also drop the loopback exception their content policy was carrying from the dev server.

Supersedes 1.5.32. That version was tagged, signed, and drafted on 2026-08-16, and its installers were uploaded to the versioned download path, but the GitHub release was never published and no one received it. The v1.5.32 tag stays as a marker. Everything from it is included here, plus the taskbar reset fix that was found in its build.

Added

  • Providers having a public outage get a badge on their card. A "0 tokens left" reading and a provider outage looked identical, so the second was read as the first. The provider card now carries the status page's own wording, plus a control that opens that page. Off by default and opt-in under Notifications, because it is the only outbound request Ceiling makes that is not to a provider you already signed in to. Nothing about you is sent. Only enabled providers whose status page can actually be read are polled, at most once every fifteen minutes, and only while a surface is asking. An operational provider gets no badge, and neither does a page that could not be read.
  • The floating bar can follow the app you are in. Pinned providers stay the default. Active shows the provider for the focused supported app or terminal agent. Active plus critical also keeps providers at or above the warning threshold. An unrelated window keeps the last active provider. Detection is local, cached, and does not call provider APIs. You can turn watching off.
  • Charts has an activity heatmap. It answers when you actually work from timestamps already on disk: a 30-day strip and a weekday-by-hour grid, machine-wide across Codex, Claude, and Grok. Nothing new is collected and nothing leaves the machine. The hourly series is built in the same pass as the daily one and is a strict refinement of it, so the calendar view and the peak-hours view cannot disagree about a total. Intensity bands follow the quartiles of the cells that have activity, so a single heavy day does not flatten the rest of the month into one shade.
  • A warning when today's spend runs far above your recent norm. A budget alert only helps someone who set a budget. This one needs no cap: it asks whether today looks unlike the days around it, which is the shape of a runaway agent loop well before the bill arrives. Off by default and opt-in under Notifications, alongside the other spend alerts, and it reads your local Codex and Claude logs, so spend on other providers is in neither today's total nor the baseline. It compares against the median rather than the mean, because one heavy day last week would otherwise raise the bar enough to hide a real spike today, and it leaves today out of its own baseline so a bigger runaway cannot make itself harder to detect. Under a dollar it stays quiet, and a baseline of zero reports nothing at all, since a fresh install and a genuinely idle week look the same from the dollars alone.

Security

  • Release builds no longer allow the webview to reach loopback ports. The shipped connect-src carried four localhost / 127.0.0.1 entries on any port. They exist for the Vite dev server and its hot-reload socket and were never needed at runtime: the webview makes no network calls of its own, and every provider request runs in Rust, including the Wayfinder gateway. Any script that ran inside the webview could previously reach every loopback service on the machine. The permissive rules now live in a dev-only overlay that tauri:dev merges in.

Fixed

  • The taskbar strip stops cutting the last character off a reset. The second line of a tile was trimmed to fifteen characters, which is a guess at a pixel width rather than a measurement of one. "Monthly · 10d 1h" is sixteen, so every monthly window with a two-digit day lost its trailing "h" and read as a countdown that was an hour out rather than as a line that had been cut, while "Weekly · 3d 23h" fit at exactly fifteen and hid the problem. The line is now measured in the font it is drawn in, against the width of the tile it belongs to. Where the whole thing will not fit it gets shorter in steps you can still trust: the countdown loses its finer unit first, so "Monthly · 23h 59m" becomes "Monthly · 23h" rather than nothing at all. That step matters most in the last day before a reset, when the countdown switches to hours and minutes and becomes the widest thing this line ever carries, wide enough to miss even the roomiest tile. Only when a tile cannot hold a coarse countdown does it fall back to the window name alone, which still says which ceiling it is; the figure is a hover away in the flyout.
  • SuperGrok's weekly percentage and reset were silently wrong. The decoder scanned the response payload looking for anything that resembled a float or a timestamp, so it could pick up the wrong field and report a confident number that did not match Grok's own meter. Both the used percentage and the period end are now read from their named fields by number.
  • Grok picks the same account every time on a multi-seat login. Account selection took whichever entry a hash map happened to yield first, so the SuperGrok meter could point at a different seat after a restart with nothing changed. It now prefers an explicit active marker, then the newest expiry, then a stable sort.
  • codexbar usage with no provider flag now reads the providers you enabled. It was documented as printing your enabled providers and always queried Claude regardless. It resolves through the same enabled set the desktop app uses. -p all and -p <provider> are unchanged.
  • Spinner arrows, the caret, and selection highlights follow the app theme. Those three parts of a form field are painted by the engine rather than by our CSS, and they were left to inherit color-scheme from the root. The app theme and the Windows theme are independent settings, so that inheritance was one refactor away from a light spinner on a dark field. Each control now sets it directly.
  • The last glow in the app is gone. One rule still drew an 8px halo on the promo boost chip, against the rule the UI doc sets out. It is now a zero-blur accent ring, which keeps the emphasis. A prebuild check fails the build on any new shadow with no offset and a non-zero blur, and it self-checks so a detector that stops working fails loudly instead of passing quietly.
  • Linux and WSL look for sessions where they are actually kept. Cursor's session database is read from ~/.config/Cursor before the old guess, Claude Desktop's from ~/.config/Claude, and the Windows profile behind a WSL install is no longer inferred from directory listing order, which could silently pick the wrong user. CEILING_WINDOWS_USERNAME names it explicitly when the guess is not wanted. Windows paths and the existing overrides are unchanged.
  • A crash on Linux or WSL no longer leaves a lock nothing can clear. The state-write lock was a file created exclusively, so a second writer failed on the spot instead of waiting, and a process killed outright left the file behind for good. It now takes an exclusive flock, which waits its turn and is released by the kernel when the process dies however it dies. Windows locking is unchanged.
  • Opening Settings from the floating bar no longer lands on General. The bar asked for a menuBar tab the Settings window does not have, so the window fell back to General instead of Display. It now opens Display, the same tab the dashboard already uses for that action. The lists that name a Settings tab on each side of the bridge are compared in CI so a renamed tab cannot silently send you to General again.
  • The OpenCode mark now matches the real brand, and stays visible on a light taskbar. OpenCode's square-ring logo is monochrome, black on white or white on black, but nothing showed it that way. The taskbar strip painted it in an invented blue, and the dashboard drew it in the near-black taken straight from the brand asset, which left it almost invisible against a dark card. The strip now paints whichever half of the brand the taskbar behind it can actually show, which also fixes the Grok monogram beside it: both were a fixed near-white that disappeared on a light Windows theme. The dashboard cannot switch per theme the same way, so it takes one mid gray that clears both, the same one its chart bars use; the bundled SVGs now take their fill from that value instead of carrying their own. OpenCode Go charts pick up that gray too, having previously fallen through to the generic cost blue.
  • Cursor no longer treats missing usage as zero. An empty individualUsage object used to paint a 0% monthly bar and hide a real team pool sitting next to it. Monthly is now marked unavailable when Cursor reports no reading, and an empty individual object falls through to team usage. Glance surfaces no longer paint a 0% Plan bar when monthly is unavailable — Overview, flyout, detail, floating bar, and the native taskbar tile show the named state instead. On-demand stays billed spend; plan and included dollars are labeled Included so they are not read as an invoice. A missing Composer tracking database is shown as unavailable, not as no activity.
  • Leftover English on glance surfaces now goes through locale keys. Floatbar settings, freshness chips, account-status labels, Charts tab names, and About update copy used hardcoded English. They now use en-US.ftl (and zh-CN) so chips no longer show raw stale / error tokens.
  • Refreshing model prices no longer blanks out the prices you already had. The models.dev price cache was emptied before the new copy was written, so a second Ceiling process reading during that moment saw nothing, and a crash mid-write threw the cache away for good. Either way token costs quietly disappeared until a network refresh worked. The new copy is now written beside the old one and swapped in, so there is never a moment with no prices on disk.
  • A Claude token refresh no longer replaces a symlinked credentials file. Claude Code owns .credentials.json, and people who manage it with chezmoi or stow, or who share one file between WSL and Windows, keep a symlink at that path. Writing a refreshed token used to drop a fresh private file over the link, so the real file kept the old tokens and the next chezmoi apply signed Claude Code out. The refresh now follows the link and keeps the file's own permissions. It also takes a per-file lock on the shared file rather than on each path that points at it. If the app and the command line refresh the same login at the same moment, Claude retires the token the slower one used, so that one now steps aside and picks up the tokens that actually work instead of writing back dead ones and signing Claude Code out. Gemini and Grok already worked this way.
  • The chart cache no longer grows forever. Its keys include the reset window they were built for, so every provider reset started a new entry and left the old one on disk for good. Someone who opened Charts after each reset added entries every day, and all of them were read back at startup. An entry is now dropped once it is two days old, which is what retires a rolled window; a 256-entry ceiling sits behind that as a backstop and should not be reached on a normal machine. A cache file left oversized by an earlier build is trimmed and rewritten on the next launch, and so is one written in an older cache format or one that no longer reads back.
  • The command line stops filling your temp folder with log files. Every codexbar run wrote a small log named after its process id and never removed it. The statusline command runs once per editor redraw, so an active day could leave thousands of files behind, and Windows does not reliably clear temp. Logs now go to a private folder of your own instead of the shared temp folder, a run that works removes its own log, and statusline writes none at all. Logs from a run that was killed are cleared after a day. A run that genuinely fails still keeps its log so there is something to read. A typo like a wrong command does not, since the error is already on screen. The files older versions left in your temp folder are swept up on the first runs after this update, and after that the temp folder is not scanned again. On Linux and macOS the new folder is created private to your account, so nobody else signed in to the same machine can read those logs or leave anything in it for codexbar to write into.

Internal

  • Cursor usage-summary and Composer-activity paths now have deterministic fixtures for normal, partial, duplicate, and malformed data.
  • The Claude credential persist is now covered on Windows, the platform its CI runs on: the rule that settles two concurrent refreshes, the file keeping its own security descriptor, and a symlinked credential path where the account may create one.

What's Changed

  • Credit @diogochaves in the 1.5.31 changelog by @tsouth89 in #293
  • Stop treating missing Cursor usage as zero by @tsouth89 in #294
  • Localize leftover English on glance surfaces by @tsouth89 in #295
  • Add active and active-plus-critical floatbar modes by @tsouth89 in #296
  • Add an activity heatmap to the Charts tab by @tsouth89 in #298
  • Probe Cursor IDE state.vscdb under Linux config_dir first (SBS-849) by @tsouth89 in #304
  • Select Grok auth.json accounts deterministically, not HashMap order (SBS-852) by @tsouth89 in #307
  • Honor enabled providers for default codexbar usage (SBS-855) by @tsouth89 in #309
  • Decode Grok weekly used% and reset from named proto fields (SBS-854) by @tsouth89 in #308
  • Probe Claude Desktop session under Linux ~/.config/Claude (SBS-850) by @tsouth89 in #305
  • Drop localhost CSP wildcards from release builds by @tsouth89 in #297
  • Align CEILING_UI.md with shipped behaviour and drop the last glow by @tsouth89 in #301
  • Badge providers that are having a public incident by @tsouth89 in #300
  • Stop guessing a Windows profile from WSL readdir order (SBS-848) by @tsouth89 in #303
  • Pin engine-drawn control parts to the app theme by @tsouth89 in #302
  • Warn when today's spend runs far above the recent norm by @tsouth89 in #299
  • Use flock for Linux/WSL state-write lock so crashes cannot stick (SBS-851) by @tsouth89 in #306
  • Bound the persisted chart-data cache (SBS-887) by @tsouth89 in #312
  • Clean up CLI launch logs in temp (SBS-888) by @tsouth89 in #313
  • Preserve permissions and follow symlinks on Claude credential persist (SBS-883) by @tsouth89 in #310
  • Replace the models.dev price cache atomically (SBS-870) by @tsouth89 in #311
  • Open Display from the floating-bar Settings action (SBS-872) by @tsouth89 in #314
  • Document omitted float-bar selection fields in SETTINGS_JSON.md (SBS-877) by @tsouth89 in #316
  • Stop painting Cursor unavailable Plan as 0% on glance (SBS-876) by @tsouth89 in #315
  • Use the real monochrome OpenCode mark, refresh marketing assets by @tsouth89 in #317
  • Release 1.5.32 by @tsouth89 in #318
  • Measure the strip's detail line instead of counting characters by @tsouth89 in #319
  • Release 1.5.33 by @tsouth89 in #320

Full Changelog: v1.5.31...v1.5.33