Skip to content

release: 0.4.0 - #47

Merged
efim0v merged 333 commits into
mainfrom
release/0.4.0
Oct 18, 2026
Merged

efim0v merged 333 commits into
mainfrom
release/0.4.0

Conversation

@efim0v

@efim0v efim0v commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Grove is now part of Brow, as Workspaces, and the notch gets Glim, a pet that eats your limits. Grove's code comes in with its whole history, so this merges with a merge commit rather than a squash; the changelog has the details.

efim0v added 30 commits June 13, 2026 01:01
…s accounts

The wrapper was a single shared scriptDir/grove-statusline.sh with each
account's GROVE_USAGE_DIR/GROVE_ORIG baked in, and every account's settings.json
pointed at it — so enabling monitoring on a second account clobbered the first's
baked env (account A would write usage into B's dir and run B's statusline).
Ship one wrapper per CLAUDE_CONFIG_DIR (keyed by canonicalized config dir);
each settings.json points at its own. Regression-tested with two accounts.
On macOS 26.4 the MenuBarExtra scene machinery sends terminate: to the app
moments after the status item appears (status-item visibility action →
applicationShouldTerminate), so the menu-bar app quit at launch — the icon only
flashed. Reproduced identically on the pre-change build, confirming it's the
MenuBarExtra/OS interaction, not feature code. Replace the MenuBarExtra scene
with a plain NSStatusItem + NSPopover hosting RootView (activation policy
.accessory); the manual status item has no scene lifecycle that terminates the
process, so it stays resident. Verified: process persists, 0 terminate events.
Menu bar (items 1,3,6):
- Fresh bundle id dev.artemefimov.grove — the old identity's WindowServer
  menu-bar layout cache was corrupted by the MenuBarExtra crash-loop, pinning
  the status item off-screen behind Control Center (only a reboot clears it;
  a fresh id gets a clean visible slot, proven by A/B test).
- Tree-only status item (no " Grove" text); real app bundle icon (AppIcon.icns
  + generator script).
- Global click monitor dismisses the panel on an outside click.

Perf (item 2):
- refreshUsage now runs its file I/O + JSON parsing OFF the main actor and
  concurrently with the scan, so a cold transcript parse no longer freezes the
  panel on open.
- Persistent ClaudeService / UsageAnalytics on AppState so their mtime
  parse-caches survive across scans (a fresh instance per 15s tick re-parsed
  every transcript). Marked @unchecked Sendable (lock-guarded caches).
- os.Logger timings (subsystem dev.artemefimov.grove).

Charts data (item 4):
- AccountUsageAnalytics.daily: 7 calendar-day token buckets for the Daily
  Usage chart, with test.
Item 4/9 (charts): a native Swift Charts usage dashboard matching the reference
— 5-Hour / Weekly limit bars (progress bar + reset + on-track/limit-close),
Usage Rate line chart, Daily Usage bar chart (Sun…Today, coloured by intensity),
Token Usage table + per-model breakdown. Overall + per-account columns side by
side. Pure, fully-tested presentation models (DashboardPresentation).

Item 4/23 (two-tab shell): RootShell hosts Projects | Charts tabs; the selection
lives in AppState so it survives the panel closing (state preserved on minimize).
Panel width adapts per tab.

Item 4/7/24/25 (projects): the Projects tab is the primary view — each project
card shows its 2 most recent Claude sessions as distinct, tappable blocks with a
running/waiting/closed status and a Go/Resume terminal target. Powered by a cheap
bounded recent-session indexer (ClaudeService.recentSessions) + a presentation
join (ProjectSessionRows), refreshed off the main actor — no git scan needed for
the routing flow.

Snapshot: new charts.png scene + fixture session previews + daily data; projects
scene resized for the new shell.
- Render every snapshot scene end-to-end (testAllScenesRenderToNonEmptyPNGs):
  drives every routed view body, doubling overall line coverage.
- DashboardRenderTests / ViewStatesRenderTests: live Swift Charts paths,
  empty/no-data/scanning/search/error-banner branches.
- ProjectSessionRows + AppState.openSession/refreshSessionIndex tests.
- CmuxAppleScript backend tests via a scripted runner (list/select/close/error),
  plus the daily-usage and recent-session-index tests from the feature work.
- Latency guard: the Projects-tab indexer stays < 0.5s with 80 transcripts.

452 tests; new feature code at 92–99% line coverage.
Lifts total line coverage to ~85% (87.5% excluding the NSApplication entry
point and the real-socket cmux probe). 453 tests.
Adds targeted tests closing the gaps in the pure/mockable logic layer:
- OAuthUsageClient: noCredentials/http/malformed/backoff-suppression/int-util.
- ClaudeService: custom-account identity + organizationRateLimitTier, process
  validator toggling live processes, launch-command env prefix.
- GroveError: every case's description.
- AppState: project/account CRUD, model/effort setters, aggregateRemaining,
  monitoring toggle, cmux go/open-shell.
- Config: minimal-JSON back-compat decode + full round-trip.

Scripts/coverage.sh + COVERAGE.md define the policy: the unit-coverage GATE is
the logic layer (services/models/presentation/state) at ≥95%; SwiftUI views are
validated by snapshot/render tests, and real cmux/osascript/socket adapters +
the NSApplication entry are validated by manual/integration testing.
Root cause of the jump: the panel was an NSPopover, which re-positions relative
to the status button on every content-size change — so each tab/route switch
(Projects↔Charts↔project scope) re-centered it. Replaced with a borderless
GrovePanel pinned by its TOP-RIGHT corner to the icon: any resize grows it
left/down, never jumps, and it's exactly right-edge-aligned (item 6).

Fixes from the adversarial review of the panel change:
- Drop .nonactivatingPanel — it blocked the panel from becoming key, so the
  search field got no keyboard. Plain .borderless + canBecomeKey + activate.
- Observe preferredContentSize (KVO) and setContentSize + re-pin — an NSWindow
  does not reliably resize itself to a contentViewController's preferred size,
  so tab switches would have clipped instead of resizing.
- invalidateShadow() on resize so the rounded window shadow tracks the shape.

Runaway-CPU fix (also a real leak): RootView's 15s refresh .task kept running
after the panel hid (orderOut doesn't cancel a .task) and in headless renders —
spinning forever against the real ~/.claude. Gated it on AppState.isPanelOpen
(.task(id:)), set by the controller on show/hide; false in tests/renders so the
loop never starts. Full suite back to 41s (was hanging).
Claude Code runs statusLine.command via `sh -c`, and the production scriptDir
(~/Library/Application Support/Grove/bin) contains a SPACE. StatuslineInstaller
wrote the wrapper path UNQUOTED, so every render failed with
"…/Library/Application: not found" (exit 127): the wrapper never ran, no
rate-limit snapshot was ever written, and the dashboard showed zero limits for
every account. Now the command is shell-quoted (and re-install recognises both
the quoted and legacy-bare forms). Regression test runs the installed command
through `sh -c` with spaces in the path and asserts a capture is written.
Real Claude captures send rate_limits.*.resets_at as a Unix epoch number, but
UsageReader only read it as an ISO8601 string -> the limit % showed but the
"Resets in" countdown was always blank. normalizedResetsAt now accepts an ISO
string OR an epoch (seconds, or ms) and emits an ISO8601 string the rate-limit
model parses. Tests cover both forms.
- Charts tab now shows ONE scope at a time (Overall / each account) chosen with
  ‹ › arrows (state.chartsScopeIndex, persisted). Same widgets, different scope.
- No scroll: the panel is a single ~320 column with adaptive height, sized to fit
  all cards (RootView charts frame is width-fixed/height-adaptive, cap 820).
- Removed the Usage Rate line chart. The per-session capture data can't support a
  trustworthy usage-over-time line: sessions cache their own last-seen limits, and
  many captures share one capturedAt with different (often stale) values — which
  drew the bogus near-vertical "spike".
- Fixed the underlying staleness: currentWindow() now selects the latest capture
  whose resets_at is still in the FUTURE, so the 5h/weekly bars (and the aggregate)
  show the CURRENT value (e.g. 23%/5%) instead of a stale already-reset 53%/88%.
  Tests cover fresh-vs-stale selection and the all-stale fallback.
1. Token Usage columns span the full card width (evenly distributed, was bunched).
2. Charts and Projects tabs share one width (460) — identical, non-truncating
   header/footer, no width-jump on tab switch.
3. Graph: a branch takes its horizontal offset in a SHORT rounded bend near the
   lower-lane endpoint and is vertical otherwise — no gradual diagonal drift.
4. Limit cards show the absolute reset time ("Resets in: 59m at 10:03 PM"); Daily
   Usage labels its unit ("tokens / day") and shows per-bar detail on hover.
5. Corner radii scaled back to standard macOS rounding (panel 16 / card 12 / field 8).
6. Sessions are now a distinct, bordered "Sessions" sub-table with detail
   (status · title · account · model · tokens · cost · age · Go/Resume), set apart
   from the workspace repos; junk caveat titles fall back to a short id.
7. Account card: identity (email · org · tier) on its own full-width row instead
   of wrapping into the controls, which move to a separate aligned row; Remove is
   snapshot-safe. Account limit bars also use the stale-aware currentWindow.

Charts snapshot scene widened to 460; DesignRadius tests updated. 479 tests pass.
…footer

Charts is now a ~260 column (≈half the previous 460). To keep the shared
RootShell header/footer clean at that width: the brand is the tree mark only
(no "Grove" text), the tab strip is text-only chips (no wrap), the footer version
is "v0.1.0" with fixed-size labels (no more "Ac/cou/nts" wrap). Token "This Month"
row shortened to "Month" so the table fits the narrow column. Projects stays 460.
…s tag, standard corners

Item 2 — session statuses were бутафория: Claude Code writes live status
"busy"/"idle"/"shell" (never "waiting"), and buildProjectSessionRows mapped both
busy AND idle to "running". Now only "busy" is running; "idle"/"shell" read as
"waiting" — the finished-its-turn, awaiting-your-prompt state you route to.

Item 1.1 — Token Usage numbers and the Daily-Usage hover truncated ("$78.1…") at
the narrow Charts width. Fixed the layout: a narrow fixed label column so the four
numeric columns get the room, scale-don't-truncate (no dots), shortened model ids
("claude-sonnet-4-6" → "sonnet-4-6"), and widened Charts 260→290 — the narrowest
width where a 4-number row stays fully legible.

Item 4 — added an overall-limits tag to the header: "5h 62% · 59m" = tier-weighted
remaining capacity across accounts + the SOONEST upcoming reset (the next time any
account's window refreshes). New AppState.aggregateReset documents the principle.

Item 3 — corners reduced to standard macOS rounding (panel 10 / card 6 / field 5,
was 16/12/8) so text no longer crowds the curve.

Tests updated (radii, charts size, idle→waiting) + added aggregateReset and
shortModelName. 481 pass.
…ts the statusline can't cover

Item 1.3 — work-account showed no limits (and Overall looked like one account)
because Claude Code's statusline emits no rate_limits for a lightly-used custom
account; its limits exist server-side but never reach the local capture. Grove now
reads them straight from api/oauth/usage, authenticated with the account's own token.

- ClaudeCredentials: KeychainCredentialsReader resolves the account's token from the
  macOS Keychain. Reverse-engineered Claude Code's service-name scheme — default
  account → "Claude Code-credentials"; custom CLAUDE_CONFIG_DIR →
  "Claude Code-credentials-<first 8 hex of sha256(configDir)>" (confirmed: the apple
  dir hashes to a0e8383e). Legacy .credentials.json kept as a fallback.
- OAuthUsageClient now takes a CredentialsReading (Keychain by default) instead of
  reading a fixed file; existing file-based tests pass via the fallback.
- AppState.refreshUsage consults the OAuth API ONLY for accounts whose statusline
  gave no fresh window (so the default account triggers no extra call/prompt), and
  folds the result into snapshotsByAccount as a synthetic capture — so the limit
  cards, the aggregate, and the header chip all light up uniformly. utilization is a
  0–100 percentage (verified against the live endpoint), clamped, not scaled.

Cached ≥3min with 429 backoff (already in the client). First read of another app's
Keychain item prompts the user once to allow access.

12 new tests (credentials scheme + token parse + OAuth fold-in + skip gate). 493 pass.
The usage dashboard is now a SECOND always-on borderless panel docked to the LEFT
of the main panel (tops aligned), opening/closing with it and re-gluing to its
left edge on every resize/move — so charts stay visible beside whatever section
the main window shows. The Projects|Charts tab strip is removed; the main header is
now the brand + overall-limits chip + add.

Adversarial review (5-dimension multi-agent pass) found and this commit fixes:
- Charts panel no longer becomes key (new ChartsDisplayPanel, canBecomeKey=false):
  the main panel keeps key focus, so the search field and ⌘R/⌘Q keep working while
  you click the charts' scope arrows (buttons respond without key status).
- Positioning is fully clamped: docks left, falls back to the right when there's no
  room, and both axes are clamped on-screen; the window height is capped to the
  visible screen so a tall dashboard never pushes its top above the menu bar.
- windowDidResize ignores resizes delivered while a panel is hidden (no stale-anchor
  repositioning).
- Removed now-dead RootTab/rootTab state.

Also: a Token Usage tooltip documents the counting method (each message id counted
once for the calendar month; resumes replay history but aren't re-billed) — verified
against the transcripts: 50% of records are exact replayed duplicates, so tools that
count every copy report several× higher. Grove's figures are the billable ones.

493 tests pass.
…session trend

Visual overhaul toward the reference:
- The WINDOW is now real Liquid Glass (.glassEffect), transparent — not a flat
  blur. Both panels (main + Charts) use it; RootView's dark scrim drops to a faint
  0.12 so the glass shows through.
- Content blocks are GRAY translucent cards (GlassCard no longer uses glass), with a
  pronounced iOS-26 squircle: card radius 6.9 → 10.5 (+~50%), field 5.75 → 8.5,
  window 14.
- Tighter margins: dashboard padding 12 → 8, card padding → 10.
- Limit bars thinner (height 9 → 5); daily-chart bars narrower (ratio .6 → .42).
- Limit-card titles smaller (caption, full opacity — like "Resets in" but solid).

New dashboard content:
- Weekly Sonnet limit card (the reference's third bar). The statusline never emits
  it, so it comes from Anthropic's OAuth usage API (seven_day_sonnet); refreshUsage
  now folds OAuth limits in for EVERY account (authoritative + the only Sonnet
  source), keeping statusline captures as history/fallback.
- 5-hour session trend: "avg N% · prev M%" + a signed "vs avg" delta, computed from
  the capture history (peak per completed 5h window).

These apply to BOTH the Charts window and the main projects space. 495 tests pass.
Note: the live Liquid Glass can't be shown in the offscreen snapshot renderer
(glass renders invisible there) — verify it in the running app.
…rd icons, drop header

- Status fix (B): Claude Code no longer writes sessions/<pid>.json records (that dir
  is empty), so every session read as "closed" even with 21 live `claude --resume`
  processes running. Grove now ALSO detects live sessions from the process table
  (session id parsed from `claude --resume <id>`); a recovered session shows
  "waiting" (ready for your next prompt) instead of "closed". Real status records,
  when present, still win.
- Count fix (E): refresh() now scans EVERY configured project (the selected one each
  tick, the others once), so every project card shows its "N repos · M ws" — not
  only the project that's been opened.
- Charts polish (A): limit percentages are now the same caption size as the titles
  (color, not size, conveys capacity); all card icons are a single green accent;
  Weekly Sonnet gets a distinct "Ⓢ" icon; Daily Usage and Token Usage icons accented.
- Header removed (C): the brand + limits chip made no sense beside the always-on
  Charts window. The main window now opens straight to the project list with a simple
  accent "+ Add project" row at the top; the footer stays.

497 tests pass.
…t set, tidier session rows

- Content substrates (both windows): ~20% darker + ~10% more transparent
  (white .20/.78 → .16/.70), NO light glass border, +20% corner radius
  (card 10.5 → 12.6, field 8.5 → 10.2, window 14 → 17). GlassCard drives both
  the Charts cards and the project cards, so it's consistent everywhere.
- Projects window padding reduced 12 → 8 to match the Charts window.
- Daily Usage bars are now wide with only ~3px gaps (ratio .42 → .88; snapshot
  bars fill their slot, spacing 6 → 3).
- Session rows in the project cards recomposed Apple-style: title + Go/Resume on
  the top line, "status · location · age" below, faint borderless inset (no more
  bordered card-in-card); account chip dropped as clutter.
- Effort picker now offers the FULL set from `claude --help` — low, medium, high,
  xhigh, max (was missing xhigh + max), via a shared ClaudeService.effortLevels.

497 tests pass.
…mation, launch prewarm

- Swap (H): the Charts window is now the icon-anchored FIXED reference on the
  RIGHT; the projects/navigation window docks to its LEFT and grows leftward as
  routes change — so the thing that resizes often no longer shoves the fixed one.
  Reworked positioning: anchorChartsToIcon + dockMainLeftOfCharts (both clamped
  on-screen, left-else-right fallback).
- Animation (G): route changes use a quick easeInOut cross-fade (.opacity) instead
  of a sliding spring, so the content settles cleanly while the window resizes —
  simpler and more native.
- Prewarm (I): the heavier screens render once offscreen 0.4s after launch (fixture
  state, isSnapshotRender), so the first real navigation isn't paying SwiftUI's
  cold body-build cost.

497 tests pass.
efim0v added 29 commits October 17, 2026 21:48
When cmux refuses Brow's socket, the error told the user to set a
socket password, which "Grove picks it up from ~/.config/cmux/cmux.json
automatically"; the status-line wrapper written into Grove/bin said it
was managed by Grove. Both say Brow now, as does the comment above the
cmux code.

A test reads every string literal in GroveCore's and GroveAppKit's
sources, comments skipped, and fails on one that says Grove, except the
write refused while Grove.app runs and the path of Grove's folder,
which never moved.
"Your setup comes along untouched" is true of Grove's files, but not of
what Brow starts doing. Grove.app never read the Keychain or called
Anthropic; Brow's notch reads every account it finds, Workspaces' among
them, polls the usage endpoint, keeps idle tokens fresh, and has the
claude -p fallback on by default, which spends limit and starts a
5-hour window. And with the safety net no longer restoring past Claude
Code's retention, the transcripts Grove kept putting back are swept at
Claude Code's next clean-up.

Coming from Grove (en, ru, zh-CN) gains a bullet on the notch, with
where to turn the fallback off, and tells a Grove user to raise
cleanupPeriodDays before switching to keep older sessions; the
Workspaces guide points there.
…home

Settings' launch command leaves CLAUDE_CONFIG_DIR unset for ~/.claude,
judged by the process's own home. The cards now judge it by the home
the accounts were found in, which AddAccountFlow already carries. The
app's answer does not change; the README's Settings screenshot, whose
accounts live in a made-up home, then shows the default account's
command as the app would.
The two DemoScreenshotTests each had their own accounts, and the
pictures disagreed: the notch showed Work 78 % / 61 %, Team 41 % / 93 %
and Personal 12 % / 34 %, while Workspaces' pickers, which say they
show what the notch read, showed 64/41, 22/78 and 8/19; Personal was
~/.claude in one and ~/.claude-accounts/personal in the other; Grove's
config named the accounts "work" in one and by email in the other. And
the menu bar's clock read "Fri 17 Oct", a Saturday.

A DemoFixture target, linked by the two test bundles only, holds the
accounts once: folders, names, plans, limits and their Workspaces
settings, with Personal the default account the others share into.
Both renders build from it; Workspaces' provider also hands back the
names and plans. The notch's renders run at 09:41 today, and the menu
bar's clock is formatted from that date, so its weekday is the date's.
The notch, Settings and Workspaces now show the same three accounts:
Personal (the default account, ~/.claude), Work and Team, with the
same plans and the same 5-hour and weekly use wherever two pictures
meet, and Workspaces names them as the notch does. The menu bar's clock
names the right weekday. Pictures whose pixels did not change are left
as they were.
The READMEs' layout tables count about 1,350 tests: 1,346 run in debug
and in release, six of them skipped unless asked for.
Troubleshooting said the lock "goes away with its process", which read
as the file going away. It is the lock that macOS lets go of when the
copy holding it quits, even after a crash; the empty file stays.
The test of RefreshTriggers' synchronous seed read NWPathMonitor's
current path a second time and compared the two. The path is still
being evaluated when start() reads it, so the second reading could
differ: the test failed now and then, most often in release builds,
with nothing wrong in the code.

start() now reads the path through a closure the test replaces with a
fixed answer. The test checks both answers, each against the opposite
last word, so the old code, which left that stale value until the
first callback, would still fail it.
… saved

Turning capture off edits the account's settings.json first and saves
Grove's config last, and that save is refused while Grove.app runs.
So with Grove.app running, the switch put the user's own command back
while the config still said the account was captured, and showed
"Not saved". Turning capture on, and Share sessions, did the same the
other way round: a wrapper installed, or session folders linked, that
the config never recorded.

The three actions now ask whether the config may be saved before they
touch anything, and change nothing when it may not. The message is
the same as before.
…g its files

Grove.app running pauses Workspaces only when Workspaces is wanted.
With it off, nothing changed: Settings kept the switches that give a
captured status line back, Give them back and Remove from Workspaces
enabled, though Grove.app writes the same config.json and settings.json.
Turning capture off then edited settings.json and could not save the
config (the previous commit makes that change nothing at all).

Settings now disables those controls while Grove.app runs, whether
Workspaces is paused or off, and the note under the account cards says
to quit Grove. The host refuses such a change before it reads or writes
anything, and follows Grove.app starting and quitting through the
running-apps watch it already keeps.
What went wrong giving a status line back, or removing an account
whose folder is gone, while Workspaces is not on was one value, shown
under every capture switch and every gone-account row, so one
account's failure appeared under all of them.

Each failure is now kept by the account it concerns and shown under
that account only. Give them back, in Settings › Workspaces, lists the
failures of its accounts, each once: Grove.app running fails them all
the same way. A failure goes when its account leaves the list. With
Workspaces on, giving back shows its failure there too, instead of in
the panel's banner.
Workspaces calls an account by the name of the notch's card that
shares its folder. The notch folds one organisation's folders into one
card, and Grove's config can hold an account for each: both then read
"Work" in Resume as…, Migrate to…, New Claude, the session rows and
the project's default account, where Grove's own names had told them
apart.

The first of them in Grove's config keeps the card's name, as
Settings › Accounts joins the first to the card; the others keep
Grove's names, which Settings lists them under. Messages from a change
made with Workspaces off name the account the same way.
docs/workspaces.md said that while Workspaces is off no file is read or
written for it, and docs/architecture.md that no state exists, so not
even Grove's config.json is read; WorkspacesHost's own summary said the
same. Neither is true since a captured status line can be given back
with Workspaces off: Claude Code keeps running the wrapper, which keeps
writing <account>/grove/usage, Settings reads Grove's config to list
those accounts, and giving one back builds a state for that one change.

Both docs now name that exception, as Settings, the README, SECURITY.md
and the changelog already do. The architecture notes also say that the
capture switch and Share sessions change nothing when the config could
not be saved, and that Settings waits for Grove.app with Workspaces off.
The render draws the safety net on and two accounts capturing their
status line, a setup Grove made, but its host had no Grove setup
recorded and no config on disk, so the text above said the safety net
"starts off for a new setup". True for a new setup, and still read as
the contradiction it was meant to remove.

The demo's settings now record a Grove setup, so the render carries
the words a Grove setup is told, and a test that runs with the suite
holds the render to that.
The README's picture of Settings › Workspaces draws a setup Grove made,
the safety net on and accounts capturing, and now says what such a
setup is told: it runs the projects, accounts and settings you had in
Grove, and the safety net keeps the setting Grove gave it. Every other
README picture renders the same as before and is left as it was.
Glim lives in the black beside the notch, in the strip below it, or in
the pill on a display without one, and eats what your Claude Code
accounts use: when the 5-hour number goes up, it takes a bite. Hours at
the Mac without Claude Code make it hungry; time away never does. When
the 5-hour limit is used up it falls asleep full until the window
resets, and when the weekly limit is used up it hibernates in its den
behind the camera until the weekly reset.

Hover the notch and it hops into a new first row of the panel, with its
state, its last meal and a hunger meter; a click on it there boops it.
It draws only while someone can see it, follows Reduce Motion, Reduce
Transparency and Increase Contrast, and keeps its state in pet.json.

It is on by default. Settings › General › Show the pet turns it off,
and with Show Brow in the notch off there is no pet at all.
The BrowKit line counts Model/, State/ and all of Pet/, the pet's
views, senses and perf log included, but its comments and COVERAGE.md
called it models and state without the views. They now list what it
measures, and the line reads "BrowKit coverage" rather than "BrowKit
logic coverage". The scope itself is unchanged.
The linker's debug map in Brow and in brow (its N_OSO and N_SO entries)
named every object file and source folder by its absolute path on the
build machine. build-brow.sh now strips it with strip -S before
signing; nothing reads it at run time and no dSYM is made from these
copies. release.sh refuses a binary that still names the source tree
or carries the map.
The notch, the panel and the menu-bar item hung from a blue, purple and
peach gradient; they now hang from cream, the colour of Glim, as the
animations and the website do: #F3E8D2 under a #FAF4E7 menu-bar band of
the same height, both flat. White glyphs all but vanish on that band, so
the menu-bar item is drawn dark, the way a light menu bar tints a
template image, in the ink #2A2218. It comes back from ImageRenderer in
half floats, which were dithered on the way into the 8-bit PNG; they are
now rounded into an 8-bit image first, so its desktop is one colour too.
Only the five pictures with a desktop are re-rendered, and what hangs
from it is unchanged, the clock included.
The English, Russian and Chinese pages say version 0.4.0 of 17 October
2026, as the changelog does, and gain a section about Glim, the pet
beside the 5-hour readout: what it eats, its glow as its hunger meter,
its naps when a limit is used up, the panel header, and that it sends
nothing and reads no transcripts. The privacy list and the questions
say the same.

media/glim.webp and media/glim-still.webp are copies of notch.webp,
holding the place of Glim's renders.
The closing paragraph about Workspaces becomes a section of its own,
before the install notes, on the English, Russian and Chinese pages:
what a workspace is, the two ways to move a session to the account with
room, where to find Workspaces (Brow's item in the menu bar or the
button in the notch panel), the brow tool with grove as its second
name, cmux or Terminal.app, and Coming from Grove. Two screenshots from
the README go with it: a project's workspaces and the launch sheet with
each account's limits.

The hero names Workspaces too. The install notes add the brow tool and
how to start Workspaces, the requirements add git and cmux, and the
checksum line takes --ignore-missing now that SHA256SUMS.txt covers
brow-cli.zip as well. The questions add the menu-bar item macOS hides
beside the notch, and the structured data lists the new features.
Grove's two guides become Brow pages, rewritten for 0.4.0: Run Claude
Code across multiple repositories (/brow/multiple-repositories/) and
Continue a Claude Code session on another account
(/brow/move-session-to-another-account/). Workspaces opens from Brow's
menu-bar item or the notch panel, the command-line tool is brow with
grove as its second name, accounts are added and shared in Settings ›
Accounts, every account picker shows that account's limits, a start
point only origin has is taken as origin/<name>, and the one-writer
checks look for the session's folder as well as its ID. The comparison
with claude --worktree from Grove's landing page joins the multi-repo
guide.

The landing pages, the accounts guide and the 404 page link to the new
pages instead of the Grove site, and the sitemap lists them. The
screenshots are the README's, as WebP.
Its structured data becomes the same graph as the two guides that moved
from Grove: a TechArticle with its own @id, and a BreadcrumbList from
Brow's page to the guide. The head gains article:modified_time.
The app icon's shaded grey tile, the website's favicons and the margin around the Copy my week picture turn flat cream, #F2DFB8; the shape, the notch and its two dots stay as they were.
Brow's colour becomes the cream of its pet Glim. The mark in the header
of every page is a cream tile, #f2dfb8, instead of grey, with the same
black notch and green and orange dots. The Download button and the skip
link are cream with #2a2218 text and a faint edge, in light and dark
alike; hover and keyboard focus darken the cream, and focus keeps its
ring. Page colours, the pictures and the favicons stay as they are.
The hero, the calendar and the sign-in loops are rendered again from the
app's own views, with Glim in the strip and in the panel's header, on the
cream desktop of the screenshots. A new section shows Glim's day: two
meals, asleep at the 5-hour limit, into its den at the weekly limit and
out again at the reset. The social preview shows the panel with Glim.
The site's hero, calendar, sign-in and Glim loops, the notch with Glim between the readouts, the Accounts settings with their Workspaces section, and og.png, with alt texts that say what each one shows.
A write to history.jsonl within a quarter of a second of the last prompt was folded into it, so three quick appends whose events reached a slow machine 0.3 s apart counted as two prompts. Each write is now measured from the write before it.
@efim0v
efim0v merged commit c7f59ce into main Oct 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant