Improves clarity and consistency throughout mobile pairing, settings,
and permission descriptions. Makes the distinction from Tailscale
more explicit where relevant. Updates all translated locales.
(cherry picked from commit 5dc6799c48)
Inline setup/onboarding terminals (skill installs, feature tips) and the floating terminal use host-agnostic synthetic worktree ids with no worktree/repo row. Since #9994, connectPanePty gated their transport on resolveWorktreeOperationRouteResult, which fails closed ('missing') for unknown ids — so the Orca CLI skill Update button, every other inline setup terminal, and the floating terminal showed "Workspace identity is ambiguous across hosts" instead of running.
Route them through the shared resolveTerminalWorktreeRoute (which already exempts the floating terminal and folder workspaces), and add the missing ephemeral-setup exemption so setup terminals follow the single active runtime (remote skill installs land there) or run locally when none is focused. Genuinely unknown/stale repo-backed worktrees still fail closed, preserving #9994.
Adds terminal-worktree-route unit tests and connectPanePty regression tests (proven to fail without the fix).
Co-authored-by: Orca <help@stably.ai>
(cherry picked from commit 8a23618310)
* fix(terminal): resume output after recovery resync
* fix(terminal): harden recovery resync boundaries
* fix(terminal): prevent double rendering on frame-drop resync
After an untagged resync snapshot, the client resets to the snapshot's output
high-water. Buffered output generated while serializing is already included in
the snapshot, so replaying the entire buffer afterward renders those bytes
twice. Trim to output after the snapshot seq.
Tagged snapshots feed side consumers and need all buffered output unchanged.
Add exponential backoff for truncated recovery retries to avoid stampeding
flooded servers.
* Revert "Enable accessibility tree (`ax`) command on iOS emulator sessions (#10007)"
This reverts commit 43ae014a64.
* fix(emulator): expose iOS accessibility tree
* fix(emulator): support device-only iOS AX
* fix(emulator): normalize iOS ax to 0..1 and heal missing axUrl
serve-sim's helper /ax reports element frames in absolute pixels, but
tap/gesture take normalized 0..1 coords. Normalize the raw AX node tree
into a compact nested shape whose frames are 0..1 over the device screen
(first root's frame), mirroring serve-sim's own normalizeAxTree, so agents
can feed ax output straight back into input commands.
Also heal sessions that were registered without an axUrl: #9924 only
derived /ax at parse time, so already-active sessions had no endpoint.
The bridge now derives it from the session's mjpeg stream URL, guarded to
the /stream.mjpeg suffix so a non-mjpeg URL never fabricates a bogus /ax.
* docs(emulator): mark ax working on iOS with correct raw-AX-tree shape
Both skill guides and the CLI summary described iOS ax as unsupported (or,
via the reverted #10007, as a normalized "screen + elements" shape that
never matched the endpoint). ax works on both backends: Android via
uiautomator, iOS via the serve-sim helper. Document the real iOS output —
a raw AX node tree (labels, roles, nested children) with frames normalized
to 0..1 — and regenerate the bundled skill guides.
* chore(skills): regenerate skill bundle manifests
CI verify failed because generated skill artifacts were stale after version/skill revision bumps.
* fix(emulator): read ax from explicit device without active session
Fall back to udid-keyed session lookup when a worktree has no active emulator,
allowing `--device` targeting to work the same way for ax as it does for tap/type.
Also clarify in docs that AX frames are normalized 0..1 with top-left origin,
and show how to tap an element at its frame center (x+width/2, y+height/2).
* fix(emulator): cap iOS AX tree at 500 nodes
Unbounded accessibility trees can flood agent output. Enforce a 500-node limit (matching serve-sim's snapshot cap) and mark truncated parents so consumers know the tree was cut.
---------
Co-authored-by: 5Hyeons <ohs2251@naver.com>
Update desktop experimental settings, mobile settings/onboarding, i18n
(en/zh/ja/ko/es), and user-visible error strings. Keep internal APIs and
identifiers as nativeChat.
Coalesce identical and sub-pixel fallback overlay measurements so ResizeObserver and xterm fit cannot sustain a render feedback loop, while preserving precise committed geometry.
Adds regression coverage for stable measurements, sub-pixel jitter across integer boundaries, and genuine resizes.
* fix(codex): promote [tui] settings so they survive the managed-home remirror
Codex TUI preferences (/statusline, theme, terminal title) are written into
the [tui] table of the managed runtime config.toml, but the write-back
promotion allowlist only covered four top-level scalars — so the next mirror
pass rewrote the runtime config from ~/.codex and silently discarded them.
Extend promotion to the [tui] keys the Codex TUI persists (status_line,
status_line_use_colors, terminal_title, theme), keyed as structured tui.*
paths so the same three-way merge (runtime vs baseline vs ~/.codex) applies:
in-Codex changes promote into ~/.codex before the mirror, and outside edits
to ~/.codex still win over stale runtime values.
The byte-preserving upsert moves to codex-config-settings-upsert.ts (max-lines)
and learns [tui] placement: replace an existing bare or dotted key in place,
insert into the first [tui] body, insert dotted beside existing dotted tui.*
keys, or create one [tui] table at EOF — never defining tui twice, including
when the system config holds an inline tui = {...} table.
* Add codex-config-settings-upsert to the CLI tsconfig file list
* fix(codex): keep tui upserts out of array tables
* fix(codex): handle quoted tui config paths during promotion
* fix(codex): harden tui promotion writes
Allow the existing "Open in" entries to launch a configured VS Code
launcher against an SSH-backed worktree via Remote-SSH:
code --remote ssh-remote+<authority> <remote-path>
- Split the blanket SSH/runtime block into a capability model: file
managers and non-VS Code launchers stay local-only (disabled with
"Local only" metadata); a recognized VS Code command is enabled and
forwarded with connectionId over a typed object IPC.
- Main process stays authoritative: rejects active/owned runtimes,
resolves the SshTarget from the persisted Store, derives the authority
(config alias, or username@host on port 22, or ssh-alias-required on a
non-default port), validates POSIX/Windows absolute remote paths without
local stat/normalize, and rejects non-VS Code and compound commands
before spawn.
- Authority and remote path are passed as separate argv; getSpawnArgsForWindows
remains the cmd/bat shim boundary and fails closed on metacharacters.
- Same capability rules across the worktree menu, Explorer overflow, and
the source-control entry context menu.
Refs STA-2386
Closes#9999
* Enable accessibility tree (`ax`) command on iOS emulator sessions
Fetch the accessibility tree from serve-sim's /ax endpoint, which requires an
active session but provides the same UI snapshot capability as Android's
uiautomator output. Derive the endpoint from the stream URL when not explicitly
provided by the helper, and route through the bridge to pass session context to
the backend.
* Add ax command routing and backend integration tests
Tests verify accessibility tree routes through EmulatorBridge,
Android backend ignores iOS-specific ax URLs, and ax endpoints
are derived from serve-sim stream URLs.
* perf(runtime): idle websocket heartbeat without clients
Co-authored-by: Orca <help@stably.ai>
* fix(runtime): probe immediately when the WS heartbeat arms
Arming the heartbeat on the first accepted connection started a fresh interval,
so the first liveness ping was a full interval (~15s) out — a socket that died
right after connecting went unprobed for that window. Run one sweep synchronously
in start() so the first ping goes out at arm time; the seeded socket is pinged
(never reaped on the arm sweep) and reaped on the next tick only if it never pongs.
Tests updated for the earlier first probe.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* perf(web): pause runtime heartbeat while hidden
Co-authored-by: Orca <help@stably.ai>
* fix(web): preserve inbound-liveness baseline across a hidden heartbeat re-arm
The visible re-arm rebaselined lastInboundFrameAt=now, so a socket that went
silent while the window was hidden looked freshly-heard-from and its death was
masked for another full idle window (~25s). Move the fresh-connect baseline into
startHeartbeat (the real 'we just connected' moment) and have the visible re-arm
only reset the tick clock + clear an in-flight probe, preserving lastInboundFrameAt
so the next visible tick probes a stale connection promptly and closes if unanswered.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(agent-status): show question glyph for needs-you state everywhere
Replace the amber attention dot with the dashboard's MessageCircleQuestion
chat glyph for the waiting/permission "needs you" state across all
surfaces: the agent dashboard cards, in-app dashboard rows, the sidebar's
Agent Dashboard quick-indicator counts, the worktree-level status dot, and
the shared agent-row/terminal-tab indicators.
On the dashboard card the header glyph is suppressed when a question
summary pill is present so "needs you" reads once, not twice.
* test(agent-status): assert amber question glyph, not amber dot
The needs-you unification replaced the amber dot with the amber
MessageCircleQuestion glyph, so update the remaining state assertions in
DashboardAgentRow, WorktreeCardStatusSlot, and TerminalTabLeadingIcon to
match (lucide-message-circle-question + text-amber-500).
* test(agent-status): cover needs-you glyph surfaces
* fix(worktrees): refresh local worktrees in the sidebar while a remote runtime is active
When a remote runtime is active, a local `worktrees:changed` event for an
unbound repo was dropped by the renderer guard in useIpcEvents. Worktrees
created outside Orca for that repo (e.g. `orca worktree create` from a CLI or
automation flow) therefore stayed invisible in the sidebar until an app
restart, even though their sessions were already running.
The guard existed because an unbound repo's list fetch routes to the active
runtime (settingsForKnownRepoOwner's unbound fall-through), so refreshing with
local worktree ids could query — and purge against — the remote host.
Instead of dropping the event, pin the refresh to the local host
(forceLocalOwner): fetch the worktree list against the local owner and merge
additively. The merge is host-scoped and the deletion-purge is skipped on this
path, so it only ever adds local-host worktrees and never overwrites the active
runtime's worktree state. A genuinely-removed local worktree is reclaimed by
the next unguarded full refresh.
* test(e2e): regression — CLI-created worktree visible while a remote runtime is active
Drives the real `orca worktree create` path: the CLI RuntimeClient calls
`worktree.create` over the app's socket, registering a managed worktree and
firing the `worktrees:changed` IPC the renderer listens for. Stages a remote
runtime as active by injecting `activeRuntimeEnvironmentId` into the renderer
store, so no real remote host is needed. Fails on the prior behavior (the
worktree never appears while a runtime is active) and passes with this fix.
* fix(worktrees): pin local lineage refresh during runtime activity
Co-authored-by: Orca <help@stably.ai>
* review: trim comments to house style, normalize queue coalescing to booleans
* review: sweep rename-grace expiry before early returns in worktrees:changed handler
* review: document accepted workspace-space gap, drop imprecise 'additive' wording
* fix(worktrees): route duplicate local repo events locally
* fix(worktrees): tag local worktree events at origin, gate purge skip on runtime overlap
* test: pin origin-based forceLocalOwner with a no-runtime local event assertion
---------
Co-authored-by: brennanb2025 <brennankbenson@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
The workspace-options filter row used the Workflow icon while the
Automations nav item and page use CalendarClock. Match them so the
filter clearly maps to automation-created workspaces.
* fix(daemon): retire macOS daemons whose login session died (#7936)
A daemon that survives a full macOS logout is unsalvageable: its PAM
context can no longer host login(1) spawns (every new PTY becomes a
'Login incorrect' prompt zombie) and its Mach bootstrap namespace has
lost the system DNS resolver, so terminals it hosts have no egress.
Today it also keeps the #9301 preflight's cached 'accepted' verdict, so
it keeps wrapping spawns in login(1) forever; only a manual daemon
restart recovers.
GUI-spawned daemons now watch for login-session death from the inside:
a fresh cache-bypassing PAM probe (triggered by PTY-exit bursts, fresh
client hellos, and a slow periodic timer) must conclusively reject
three consecutive times AND the in-process system resolver must be
degraded; then the daemon exits crash-style so session meta stays
unclean and the replacement daemon cold-restores scrollback. A
conclusive rejection also flips the spawn-wrapper cache off
immediately.
Headless serve/SSH daemons never get the watch (they must survive their
spawning session ending), and a session that never conclusively
accepted login(1) never arms it — a PAM anomaly alone can't kill a
healthy daemon (fast user switching keeps accepting, so switched-away
sessions are preserved).
* test(daemon): e2e seam to drive login-session death oracles from a verdict file
A dead macOS login session cannot be fabricated without root (PAM owns
audit-session teardown), so live lifecycle QA drives the death watch's
probe and resolver oracles from ORCA_E2E_LOGIN_SESSION_PROBE_FILE:
'alive' → accepted/healthy, 'dead' → rejected/unhealthy, anything else
inconclusive — with compressed watch timing. Mirrors the existing
ORCA_E2E_DAEMON_INIT_DELAY_MS seam; inert unless the env var is set.
* fix(daemon): close the hang-shaped gap in login-session death detection
The conclusive-PAM-verdict trigger had one blind failure shape: login(1)
hanging at the prompt past the probe bound (killed → inconclusive
forever → the watch never fires). Three changes close it:
- The death-watch probe gets its own 4s bound (the 500ms preflight bound
exists for spawn-path latency, which doesn't apply off-path), so a
slow-but-answering PAM stack isn't misread as a hang.
- An inconclusive pipe probe escalates to a PTY-hosted probe via
script(1) — a dead session's PAM stack may only misbehave under a real
tty (the pipe-vs-PTY fidelity limit the preflight documents).
- A streak of timeout-killed probes (which a live session never
produces) is a second retirement trigger, at a higher threshold (5)
and still gated on the degraded resolver, logged with a distinct
cause so field logs discriminate the two paths.
Every dead-session behavior — fast reject, prompt-then-EOF, or hang —
now fires retirement; all inconclusive states still fail toward
preserving the daemon.
* fix(daemon): keep login-session retirement conclusive
* fix(daemon): stop login watch before clean shutdown
* fix(daemon): make login-session PTY probe reliable
* fix(daemon): close login-session watch races
* fix(daemon): ignore health probes for login watch activity
Production crash diagnostics measured ~128 `git worktree list` execs/min
(9,400 in one 80-minute session, ~16% of wall-clock in git subprocesses):
the resolved-worktree scan fans out over every registered repo on a 30s
cache TTL, and most registered repos on the affected installs were
agent-CLI scratch repos (~/.codex-tmp capsules, vendor imports, skill
checkouts) that need no freshness.
Classify agent-scratch repo roots with a curated shared matcher and stamp
their scan-cache entries with a 5-minute TTL instead of 30s. Orca-driven
mutations still bypass the TTL via the per-repo generation bump, so only
passive pickup of external changes slows for scratch repos. Expected
steady-state reduction on the measured install: ~82% fewer git spawns.
Zone.js patches the global Promise with a non-native thenable. When a bare
`new Promise(...)` crosses the Electron executeJavaScript boundary, it's
serialized as-is, losing { page, target } and exposing __zone_symbol__*
fields instead. Wrap in an async IIFE to return a native promise that
Electron always unwraps correctly.
The iOS MJPEG and Android scrcpy device streams are gated only on the pane
being the active tab (isActive, PR #7382). When the emulator tab is frontmost
but the whole Orca window is hidden/minimized/occluded/display-asleep, the
full-fps pipeline keeps running: main-process socket read + JPEG/H.264 decode
+ IPC + renderer decode. Renderer background-throttling (#9395) cannot stop it
because the pipeline is IPC-push driven from main.
Gate showStream additionally on window visibility via a new occlusion-safe
hook that honors the terminal stale-visibility latch (so a display-sleep
occlusion wedge can't freeze the emulator on a black frame) and delays the
visible->hidden park by 500ms so a quick Cmd+Tab round-trip doesn't renegotiate
the device stream.
Co-authored-by: Orca <help@stably.ai>
react-markdown's <Markdown> has no internal memoization: it rebuilds the whole
unified remark->rehype->highlight->katex processor and re-parses the document on
every render. MarkdownPreview re-renders on internal state that does not affect
the rendered output — most visibly, every keystroke in Find (query/match-index
state) — so a large doc re-ran the full parse + syntax-highlight + KaTeX pass per
keypress, making Find laggy.
Hoist the two fully-static plugin arrays to module scope (a fresh array identity
per render would defeat the memo) and render the body through a React.memo'd
MarkdownBody keyed on content + components. The pipeline now re-runs only when the
rendered content or the components map actually changes; Find/review-pulse/copied-
note re-renders skip it. The components map was already memoized, so its identity
is stable across those re-renders.
Behavior unchanged: 106 existing MarkdownPreview tests pass.
Co-authored-by: Orca <help@stably.ai>
* feat(mobile-pairing): surface unpaired-device auth failures instead of silent 4001 loops
Desktop: when a phone repeatedly fails direct-transport E2EE auth with a
token missing from the device registry (pre-v1.4.106 pairing-path bug left
desktops that regenerated their registry rejecting paired phones forever),
throttle to one notification per session and show an actionable toast
pointing at Settings -> Mobile to re-pair.
Mobile: map a bare 4001 close onto the existing auth retry budget (the
encrypted e2ee_error is undecryptable when the desktop keypair changed, so
the close code is the only surviving signal) instead of looping the generic
reconnect forever, and make the auth-failed verdict say 'Pairing invalid -
re-pair with your desktop' instead of a bare 'Auth failed'.
* fix(mobile-pairing): handle stale keys and startup notification races
* fix(mobile-pairing): isolate auth notification failures
* fix(mobile-pairing): keep recovery alert actionable
The live `agentStatusByPaneKey` map had no size cap. `setAgentStatus` rewrites it
with a full spread copy on every status ping and keeps rows until a pane/tab
teardown event removes them; a missed teardown (agent killed without a Stop hook,
pane/tab closed while its status lingered) orphans a row forever. On long
multi-agent sessions orphaned heavy rows accumulate without bound, and because
each ping spread-copies the whole map, once it is ~1.9 GB one ping transiently
doubles it past the 3586 MB old-space limit -> renderer crash (exit -36861).
Cap the live map at MAX_LIVE_AGENT_STATUSES=500, shedding only rows whose pane is
provably gone (a mounted tab's rooted layout no longer lists the leaf) or long
idle, and never an open pane's row (any state, incl. needs-input waiting/blocked).
Rootless/empty-snapshot, not-yet-hydrated, and no-renderer-tab (orchestration
worker) rows are unprovable: kept while a fresh agent could own them, shed only
past the stale window or by a hard-cap fallback that guarantees the bound.
Eviction bumps the status/sort epochs so the retention sync snapshots disappeared
done rows. Cost is one Object.keys length check on the reducer's already-O(n)
spread under the cap; the layout walk + sweeps run only on the rare over-cap ping.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat(source-control): show current branch in header
* fix(source-control): keep header focused on branch
* fix(source-control): compact detached head identity
* fix(source-control): make branch identity keyboard accessible
* fix(source-control): keep create review in checks
* fix(mobile): persist per-device tab selection so worktree return restores the last open tab
A phone's tab selection lived only in the host's in-memory
ClientSessionTabSelectionStore. Any host restart wiped it, and the
per-device projection then fell back to deterministic topology, so
returning to a worktree on mobile always landed on the first tab
instead of the tab last opened on the phone.
Persist the per-device selections in the Store (keyed deviceId ->
worktreeId), hydrate them when the runtime constructs, and guard
projection so an early empty snapshot after restart cannot wipe a
hydrated selection before tabs arrive. Selections are pruned with the
worktree/repo and on device revoke, and malformed persisted payloads
degrade to empty instead of throwing.
* fix(mobile): harden persisted tab selection cleanup
* fix(mobile): preserve tab selection across worktree rename
* fix(agent-status): map codex request_user_input questions to waiting
Codex 0.145 asks user questions via the auto-allowed request_user_input
tool (experimental default_mode_request_user_input): PreToolUse fires
while blocked on the answer with no Stop, so Orca showed the pane as
working/idle instead of Needs You. Map that PreToolUse to waiting
(mirrors grok's ask_user_question), exempt question waits from the codex
yolo auto-approval suppressor, and deliver native-chat answers to the
digit-commit selector by option number (typed labels are ignored and
Enter commits the highlighted first option). Older codex versions emit
no such event and are unchanged.
* fix(native-chat): preserve codex question answer semantics
The Language setting's native word (语言 / 언어 / 言語 / Idioma) only reached
settings search via the localized title in that word's own UI locale — so a
Chinese speaker on the default English UI could not find it by typing 语言.
Always-index the native word for 'language' in every supported language (plus
the previously-omitted Spanish native name Español), so speakers can locate and
switch to their language from any starting locale. Native words are
locale-invariant constants, so they are plain keyword literals with reviewed
localization-coverage allowlist entries.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-status): keep Claude in-process teammates visible as idle sidebar rows
Claude Code 2.1.21x runs named Agent-tool agents as turn-based in-process
teammates: SubagentStop and TeammateIdle fire at every TURN end while the
teammate stays alive awaiting mail (verified live on 2.1.217). Treating
those events as finish signals deleted the child row seconds after each
burst, so the sidebar showed no subagents for most of a teammate's life.
Root-cause fix: the roster now tracks a working/idle state per child.
- One-shot children (hyphen-free ids) keep remove-on-stop: their
SubagentStop is a true finish.
- Teammate-shaped rows park as idle on SubagentStop/TeammateIdle and
revive to working via the next SubagentStart (same lifecycle id,
first-observed startedAt preserved).
- Idle rows never gate the pane 'working' (#8825's done-gate rule).
- Only TeammateIdle-confirmed idle rows survive a complete lead-Stop
fold; a stopped workflow lane wearing a teammate-shaped id is reaped
there (or immediately, once a fold tagged it listedAsSubagentTask), so
the pre-#8825 idle pile cannot rebuild.
- At the wire cap, the oldest idle row is evicted to admit a working
spawn; working children are never displaced.
- Hydrate keeps pruning idle snapshots: idle-teammate liveness cannot be
proven across a restart, and a live teammate re-earns its row.
* fix(agent-status): restore inventory-confirmed workflow lanes
After a relay daemon restarts on an SSH host, main drops its status rows but
renderer agentStatusByPaneKey entries whose connectionId stamp never matched
(unstamped / SSH-mis-normalized) survived, stayed 'fresh' for 30 minutes, and
made their sidebar rows permanently un-clickable (tab gone, handleActivateAgentTab
silently returns). Broaden the transient clear to also drop worktree-attributed
rows whose owning repo is on the torn-down connection, proving the host via the
worktree->repo mapping instead of relying solely on the entry's connectionId.
Co-authored-by: Orca <help@stably.ai>
On wake, remote/SSH runtimes reconnect in a staggered burst; the sidebar refetched all worktrees once per host, piling up K synchronous full-sidebar remounts and freezing the UI. Wrap the reconnect refresh in a single-flight coalescer so at most one refresh runs at a time plus one queued rerun, for any K. fetchAllWorktrees and its lineage follow-up are unchanged; only how often they fire changes.
Co-authored-by: Orca <help@stably.ai>
* perf(rate-limits): throttle statusline usage posts to one curl per pane per 15s
The managed Claude statusline script posted on every statusLine tick
carrying rate_limits — ~3 curl spawns/sec per streaming pane, multiplied
across concurrent panes. The service drops same-value posts inside its
30s dedupe window anyway, so most spawns bought nothing.
Gate the post on a per-pane stamp file: POSIX compares date +%s against
the stamp; Windows uses an all-builtin seconds-of-day parse of %TIME%
(octal-safe, no extra process). Both fail open — unparseable time,
garbage stamp, or midnight wrap posts rather than darkening the live
usage feed — and the stamp only advances when a post actually fires, so
skipped ticks never defer the next one.
Measured: 30 rapid rate-limit ticks spawn 1 curl (was 30).
* perf(rate-limits): keep throttled statusline ticks process-free
Use Claude's monotonic session duration for the POSIX throttle so skipped ticks do not replace curl churn with date churn, while retaining a fail-open date fallback. Key temp files by the stable leaf UUID so path-like or long host tab IDs cannot disable the throttle.
* fix(rate-limits): preserve throttle isolation across upgrades
* fix(rate-limits): reject leading-zero stamp values before arithmetic
All-digits validation still admitted values like 008, which are invalid
octal inside $(( )) — and dash treats that expansion error as fatal, so
the script died before rewriting the stamp and the pane's live usage
feed stayed dark until the file was deleted. Allow-list canonical
decimals (same pattern as the duration parse) on both the stamp and the
computed clock so malformed values fail open to posting.
Verified under dash: the old digits-only check aborts at the arithmetic
(Illegal number: 008); the allow-list survives and posts.
* perf(rate-limits): eliminate POSIX statusline cat churn
* test(rate-limits): exercise overlapping statusline ticks
The stamp check/write is deliberately lock-free (a lock could wedge the
feed closed; fail-open is the contract), so a truly concurrent burst may
post more than once, bounded by overlap width — an exact at-most-one
assertion would be flaky by design. Assert the invariants that do hold:
every overlapping run exits 0, the raced stamp lands valid, and it
throttles the following ticks.