A Capacity Dock row that vanishes while hovered fires no hover exit, so the
pushed pointing-hand cursor stuck system-wide; the row now pops it on the way
out. Claude Code writes a fresh sessions/<pid>.json on every resume, so several
files name one session and the first listed was taken; the newest is taken now,
and a newest that fails the recycled-pid guard leaves the row inert.
collectKimicodeInputs recursed the whole Kimi session store to find wire files,
listing ~18k entries for ~2k wires at ~250ms per payload build. It now walks
work dir, session and agent levels only: ~23ms, and a decoy wire.jsonl under
blobs/ or file-history/ no longer reports itself as a session.
Clicking a session row finds the row's process — Claude Code writes its
pid into ~/.claude/sessions/<pid>.json, Kimi Code is found by the one
`kimi` working in the directory its state.json records — walks the
process tree to the application that owns it, selects the tab by tty in
Terminal.app and iTerm2, and raises the app everywhere else. The
AppleScript runs off the main actor with a 3s cap.
A row whose process cannot be found keeps its layout and stays inert: no
pointer change, no tooltip.
Every connected provider's windows are checked on the existing quota
refresh. A crossing fires once per (provider, window, reset instant);
the next cycle re-arms it. Authorization is requested on the first real
crossing, not at launch. Settings gains a toggle, default on.
The menubar showed a window's pre-reset percentage for up to five minutes
after its reset instant. The 30s tick now compares the reset dates it
already holds for Claude, Codex and the Capacity Dock providers against
the previous tick and forces a refresh for the group that rolled over.
A second copy of the app stacks a second Capacity Dock on the screen
edge. At launch the app now terminates instances of its own bundle
identifier that launched strictly before it, so two simultaneous
launches cannot quit each other, and forces out one that ignores the
polite terminate after a few seconds.
Scan the Kimi Code stores for session dirs whose agents/*/wire.jsonl was
written inside the liveness window and fold them into the menubar
liveSessions block as provider kimicode, with the project from state.cwd,
the model from the last llm.request in the main wire tail, and sub-agent
wire mtimes as the session's sub-agent activity.
Chips were keyed by the string they display, which truncates a session
id to 12 characters and a project path to its last segment. Two sessions
whose ids share a prefix, or two projects with the same basename on the
same branch, produced one React key for two chips: React warned about
the duplicate and could reconcile or remove the wrong one.
filterChipKey returns the composite identity each dimension is already
normalized and removed by (provider + session id, project + branch), so
chips that read alike stay distinct to React while the display label is
left as short as it was.
The list reset its visible-row count to the first page whenever the
selection, the sort, the search or the data changed — but it wrote the
internal fallback state, and the app passes visibleCount as a controlled
prop so it can restore the depth from the nav history. The reset was
therefore dead: after Show more, changing the sort or removing a chip
left the reader 240 rows deep in a list that had just been reordered.
The owner now resets it in the same commit as the change it owns, so a
sort or selection change is one history entry rather than two, and no
reset can fire during a Back/Forward restore and discard the depth that
was just restored. The effect keeps serving the uncontrolled wiring and
says so.
The Pull requests section imported the PR selection builder and threaded
the drill-through callback through four component levels, but the row's
only handler was expand/collapse, so neither was ever invoked: every
other aggregate (day, model, category, project) reached the filtered
session list and the one report that names shipped work did not.
An expanded row now carries its own "View sessions for this pull request"
control above the work breakdown, matching the project entry in Spend. It
selects the PR by URL — the aggregation key of the by-PR report — and,
being a control of its own rather than the row, never toggles the
expansion it sits in. Rows with no per-turn detail get it too, and the
section renders no affordance at all when there is no destination wired.
The popover's quota warning painted its text in the same system yellow,
orange or red as its 12% pill. In light mode that measured 1.23:1 to 1.43:1
for yellow and at best 3.04:1 for red, and dark-mode red fell to 3.33:1. The
pill keeps its hue; the text and its glyph now take a per-appearance
foreground from QuotaWarningPalette that measures at least 5.29:1, and a test
holds every severity at WCAG AA against the pill composited over both ends of
the popover's background in both appearances.
The sentence named no window. AggregateQuotaStatus reduced each provider to
its worst window and kept only the percentage, so "Claude 70% of quota used"
was the 5-hour limit read next to a weekly 34%, and looked wrong. Each warning
now carries the winning window's label and reset, and the row reads
"Claude · 5-hour 71% · resets in 3h 12m" with the Capacity Dock's countdown,
one line per provider. The countdown is left off when there is no reset time
or it has passed, and "over limit" is kept for a window at or past 100%
instead of everything the danger band (90% and up) covers. The windows
compared, the 70% threshold and the flame's severity are unchanged.
"%@ of quota used" has no caller left and is retired; "resets in %@" is added
for en and zh-Hans.
Three ServeConnectionTests failed on roughly half of all macOS Menubar CI
runs and always passed on re-run. Each one raced a real clock or a real
child process against Swift-side work. A cooperative pool starved by
CPU-heavy suites that swift-testing schedules alongside can delay that work
by seconds.
- "cancelling a hung request returns promptly" asserted a 500 ms wall-clock
budget, and its fixture child exited after `sleep 1`. When the cancellation
hop was starved past those exits, the connection retried the request on
replacement children until the death budget answered ServeUnavailable. The
timeout path never fired, because the test used the real ten-minute budget.
The child now stays alive, the timeout comes from the manual clock, and the
test asserts that the caller sees CancellationError before any timeout fires.
- "a request queued during cancelled hydration completes on the same child"
read the child's event log as soon as the reply resumed the caller, but the
child appends "second-replied" only after writing that reply. The test now
waits for the log line. The manual clock also replaces its timeout recorder,
which really fired after 5 s.
- "external cancellations keep one child and safely discard late replies" let
the fixture reply 50 ms after reading, so a delayed cancel lost to the reply.
The child now holds each reply until the test has observed the cancellation.
Polling is bounded by a 30 s hang guard instead of 2 s. When a guarded await
expires, the guard releases the fixture, so a regression fails its assertions
instead of hanging the run. No production code changes.
Closes#1333
cli-cache-read-pipeline seeded its fresh Claude turn at now-10m and now-9m
while computing "today" from the UTC clock. For the first ten minutes after
UTC midnight those timestamps land on the previous day, so the fresh slice
falls outside `--days ...,today` and `--period today`: 1811 becomes 1111 and
4321 becomes 0. A PR run that started at 2026-09-13T00:03Z failed on every
leg while main's 23:32Z run passed on the same code.
Derive every date and timestamp from one `now` and clamp both seeds into
[today's UTC midnight, now], preserving user-before-assistant order, the
same idiom cli-status-menubar already uses. The CLI subprocess has no
injectable clock, so a run that crosses midnight after seeding now skips
with a note instead of failing.
menubar-project-identity had the same bug with a one-minute window (now-1m
against getDateRange('today')); clamp it the same way.
Refs #1216