* Update PR checks fix prompt to verify failure causality before fixing
Revise the prompt to classify failures as caused by this branch, not caused,
or uncertain before making changes. Only proceed autonomously for confirmed
failures; ask the user for guidance on uncertain or unrelated issues to avoid
fixing failures that weren't caused by the branch.
* Update PR checks fix prompt to verify failure causality before fixing
- Emphasize investigation phase by reframing prompt: "Investigate" rather than "Fix"
- Extend untrusted-data warning to all investigation sources (repository files, commit messages, diffs, CI output)
- Add test verifying injection safety: malicious input confined to JSON payloads, never as prompt instructions
* Refactor buildFixChecksPrompt test to focus on field mapping
The wrapper's only responsibility is renaming mobile PR fields onto the
shared prompt builder. Remove assertions about prompt wording, which are
already covered by the builder's own test suite. Simplify the test to
verify the field mapping contract and nothing else.
Monitoring turns emit no working event (4b2e3dded0), so the titlebar
badge must not count them either, or it lights with no unread row to
clear. The build cache's cached-events ternary could never take its
cached path because the early return above already covers it.
* Fix native PTY I/O failures disabling session termination
I/O failures on write or resize were incorrectly treated as exit evidence,
which disabled further termination attempts and producer flow control.
Separate ioFailed state from dead state; I/O errors suppress operations
but keep kill, forceKill, and signal available. Publish physical exit
before notifying listeners to prevent reentrant cleanup attempts from
accessing the retired native process.
* Fix PTY I/O cleanup tests and config syntax error
- Fixed missing closing brace and comma in reliability-gates.jsonc
- Added clear() operation to PTY mock fixture and test coverage
- Enhanced assertions to verify operation suppression during I/O failures
- Improved kill operation error handling with better promise-chain assertions
- Updated test result summaries in reliability gate documentation
* refactor(activity): rank status groups by attention level
Establishes consistent group ordering by introducing an attention-based ranking system, ensuring status groups maintain a fixed order regardless of thread recency. Consolidates thread status classification logic into `activityThreadStatusId` and simplifies group key naming.
* refactor(activity): emit working state for live agent turns
Activity events now emit working state for current turns,
enabling attention ranking above historical states.
* fix(activity): preserve working turns and count as unread
- Remove working-state events from cap logic so live turns stay visible
- Count fresh working/monitoring as unread in Activity badge
- Extract state-checking to activity-event-state module
- Use agentStatusEpoch for freshness-based invalidation
* fix(activity): subscribe only to epoch for unread count, not status map
The unread receipt is keyed on turn boundaries (stateStartedAt), not
heartbeats (updatedAt). Only the epoch matters; read the status map
directly via getState() to avoid wasteful re-renders on same-turn
heartbeats.
* fix(activity): prevent monitoring turns from emitting working events
Monitoring turns should surface only via the 'monitoring' snapshot in
the live state, not as separate working events that would contradict
the snapshot signal.
* Stabilize scrollbar gutter to prevent message list layout shift
- Add `scrollbar-gutter:stable` to prevent reflow when scrollbar appears
- Adjust scroll container padding to properly accommodate the scrollbar
- Add 5px horizontal inset to content for alignment with composer field
* Simplify message list padding and update scrollbar-gutter
emitPinnedGroup was the one section emitter that appended worktree rows
without hostContextLabelByWorktreeIdentity, and the mixed-host map it
would have received was computed over naturalWorktrees, which under the
default pinned policy has the pinned worktrees filtered out. Under that
policy a pinned worktree renders only in the Pinned section, so a pinned
remote workspace had no host badge anywhere.
Compute the mixed-host map over the full worktree set and thread it into
the Pinned emitter. Single-host sidebars still draw no badge.
Fixes#18472
A paired runtime host's first status publication counted as a connection
change, advancing the connection generation. Worktree scans already in
flight against that same connection were then discarded, so the sidebar
showed a strict subset of the host's worktrees until an unrelated refresh.
Two independent defects, both fixed:
- `connectionChanged` conflated "no entry yet" with "recorded unreachable".
Only the latter is a reconnect. The provider-session bump keeps the
broader predicate, since a first publication is a real session start for
integration-readiness caches.
- A stale-generation result was thrown away with no retry, so even a
genuine mid-flight reconnect silently dropped completed work. The scan is
now re-read once against the new generation.
* fix(pi): show input modals as waiting instead of working
* test(pi): verify real input dialogs through Electron CDP
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* perf(renderer): avoid per-second spinner animation events
* fix(bench): ensure the Electron runtime before bench:spinners
The script launches Electron via Playwright but skipped ensure:electron-runtime,
which every other Electron-launching bench script runs first.
* docs(renderer): scope spinner pixel-tolerance claim to paused-animation checks
---------
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
* fix: preserve terminal retirement proof across renderer publications
* refactor: share the live-surface filter between retirement proof preservation and projection
The publication projection already dropped proofs whose surface is live;
reuse that as one helper instead of a second inline scan.
* fix: emit stored retirement proofs from host-authored snapshot writes
Three callers built a snapshot, stored it, then emitted the pre-store object. Storing grafts on the preserved proofs, so those frames carried the stored snapshotVersion without the proofs; subscribers dedupe on version and never saw them.
* fix: send terminal retirement proofs once per stream and fence them by occupant
Proofs are pinned per worktree for the host's lifetime, so every snapshot publication — including a 50ms title tick — re-shipped up to 64 proofs (~17 KB on realistic ids) to every paired client.
Negotiate session-tabs.retirement-proof-delta.v1: the host projects each session-tabs stream to send a proof only the first time that stream carries it, and a capable renderer keeps the union in a ledger keyed by (environment, worktree) with the same 64-entry bound and the same live-surface drop rule as the host, reset on removed frames and on a new connection generation. Legacy clients keep receiving the full list; CLI and mobile do not advertise the capability.
Also inherit worktreeInstanceId onto identity-less host writes so a host write between two renderer occupants can no longer launder one occupant's proofs into the next.
* fix: keep an empty proof delta distinguishable from a proof-less host
A negotiated stream now sends retiredTerminalSurfaces: [] when nothing is new instead of omitting the field. Absence is the host's "I hold no proofs" signal — which is also what a recreated worktree's fresh host entry publishes — so the client ledger forgets on absence and a successor occupant never inherits its predecessor's proofs, even when the removed frame was missed.
* test: pin ledger visibility against a legacy full-list host
An old host sends the full proof list whenever it holds any and omits the field when it holds none. Prove the new client ledger shows exactly what a legacy client would see across that sequence, so forgetting on absence is verified not to regress the mixed-version case.
* Unify sidebar create actions into a single dropdown menu
- Combine "New workspace" and "Add project" under a unified "Create" button
- Remove layout logic that split these actions based on sidebar width
- Normalize "Add Project" to "Add project" (lowercase) throughout the UI
* Use null instead of 'Unassigned' for unassigned shortcut labels
Add formatOptionalPrimaryShortcutLabel that returns null when a
shortcut is unassigned, enabling simpler conditional rendering in
dropdown menus. Remove associated translation strings.
* fix: preserve user input while terminal scrollback replays
* test: model multiple xterm user-input subscribers
* fix: keep mouse reports suppressed during replay and bind forwarders once
Real keystrokes now survive the replay guard, but xterm flags pointer
reports as user input too, and replayed bytes can leave mouse tracking
armed until the guarded mode reset lands. Keep those suppressed so a
click on restoring scrollback cannot print SGR fragments on the prompt.
Hoist the two provenance-bound forwarders out of the per-keystroke path.
* fix: keep wheel cursor keys off a replayed alt-screen frame
xterm turns a wheel notch into cursor up/down when the active buffer has
no scrollback, and flags it as user input. During a dead-TUI restore that
frame is replayed on the alt buffer and only leaves it when the guarded
?1049l lands, so forwarding those arrows would recall shell history at
the fresh prompt. Suppress them on the alt buffer only; the same bytes on
the normal buffer can only be a keyboard arrow and still survive replay.
Group the pointer-derived predicates in terminal-pointer-input-sequences.
* feat(native-chat): read a tool batch as a group
A run of several tool calls collapsed to one joined string: names and
arguments run together, separated by a middle dot that also occurs inside
`browser.open` and `tools/read`, with the overflow cut mid-token. Opened,
the member rows sat flush with the header and with the message content
around them, so the batch had no visible end.
Two presentation changes, no new derivation:
- Each member gets its own bounded pill in the collapsed header, carrying
its own category glyph, so the boundary between calls is a shape rather
than a character. Pills wrap instead of truncating, and members past the
summary cap are counted in `+N more` rather than dropped silently.
- Opened members are indented under the header, which is what marks where
the run ends.
`toolRunSummaryMembers` keeps the run's leading calls apart instead of
pre-joining them; `summarizeToolRun` now derives its string from it, so
mobile's header is byte-identical and the two cannot disagree about which
calls speak for a run.
Two existing behaviours are pinned by test rather than changed, both being
naming decisions rather than layout ones: the header still prints the raw
`mcp__linear__list_issues` while the row beneath prints the split name, and
a call carrying only a `url` still falls through to a JSON preview clipped
at 28 characters.
* fix(native-chat): bundle hidden tool count copy
* fix(native-chat): drop the filled pill for a glyph-led member list
Rendered in the app, the filled chips were wrong twice over. `bg-accent` is
reserved for hover/active row backgrounds, and the only full-strength use of
it in native chat is on payload and diff surfaces — so each member read as a
shrunken content block, and a run became the loudest thing in the transcript.
Worse, `flex-wrap` degenerated: at a 297px pane each member is 274-288px, so
every one took its own line, the header grew 24px to 72px, and the `5x` count
centred against the block landed beside the second member as though it counted
that call alone.
The glyph already marks where a member starts, so the fill was carrying no
information the icon wasn't. Members are now inline, glyph-led, and separated
by spacing; the list stays one line and truncates as a whole, as it did before
this branch. `+N more` moves outside the truncating span so the count of what
is not shown survives a pane too narrow to print the list.
Members carry `data-tool-run-member` rather than being found by their fill.
* fix(native-chat): let the run summary size to its content
`flex-1` on the truncating member list made it claim the header's slack, so
`+N more` was pushed to the far right edge with a gap between it and the last
member it counts. Without it the span still shrinks and truncates — `min-w-0`
plus the default shrink is what drives the ellipsis, which is how the header
worked before this branch — and the count now sits directly after the list at
every width.
* fix(native-chat): separate run-header members with real whitespace
An `ml-3` margin marks the boundary on screen but is invisible to a copied
selection and to the button's accessible name, so the header read
`ls -latools/read`. Adds a space text node between members and trims the
margin to pay for its width. `+N more` also picks up the hover transition
every other header segment already had.
---------
Co-authored-by: Merge Sim <sim@local>
* fix(xterm): fire Marker dispose before clearing line (#10879)
Scrollback trim under search highlights was O(k²) because dispose set
marker.line to -1 before onDispose, collapsing SortedList keys. Fire
listeners first so delete still sees the real line, then clear the line.
Fixes#10879
* fix(xterm): remove scrollback decorations by identity
* perf(xterm): avoid index arrays for unique decorations
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(native-chat): render compaction notices, plan documents, and images
* fix(native-chat): avoid repeating notice text in details
* fix(native-chat): journal canonical and legacy compaction events
* test: add digest to native chat notice payload fixture
* chore(native-chat): drop the planning doc from the PR
---------
Co-authored-by: Merge Sim <sim@local>
* fix(relay): never cache a region hint from a one-region catalog
The director lists only regions with a serving cell, so a roll wave shortens the
catalog to one entry. The resolver required only every *listed* region to be
measured, so that lone region won against nothing and was cached for 24 h: a US
desktop refreshing while US cells rolled published asia-east2 for a day, the
incident #19233 was written to end. Now fewer listed regions than the fleet serves
withholds the hint (1 h no-hint TTL), the same outcome as an unmeasurable peer.
* test(relay): give the unstable-probe case a two-region catalog so it has one cause
* fix(native-chat): scope composer file drops to the pane that received them
A native OS file drop resolving to `target: 'composer'` carried no pane
identity, so the window-wide payload was attached by every mounted composer.
Because inactive chat tabs stay mounted (hidden), one drop populated every
chat pane's attachment cache, and those chips replayed whenever the user
returned to a tab they never dropped into. The workspace-creation composer
and chat composers also leaked into each other, since neither could tell
which surface actually received the drop.
Composer drops now carry a `scopeKey` the way a terminal drop carries its
tab and pane leaf id: the composer publishes its pane key as
`data-composer-scope-key`, the preload harvests it during the composedPath
walk, and each composer attaches only its own. The workspace composer's
last-wins ownership stack now claims unscoped payloads only.
* test(native-chat): supersede the bug-asserting drop repro with the scoping test
The repro that landed on main asserts the pre-fix behavior (a drop reaching
every mounted composer), so it fails once drops are scoped to the pane that
received them. Its scoping cases now live in
native-chat-composer-drop-scope.test.tsx, which keeps its editor-target
control case verbatim and adds coverage for unscoped composers and a scope
key published inside the drop-target marker.
* test(native-chat): cover workspace composer drop isolation
* fix(native-chat): authorize external attachment paths before preview
---------
Co-authored-by: Merge Sim <sim@local>
* fix(native-chat): stop an unanswered host from reading as one that refuses structured chat
`readLocalRuntimeCapabilities()` returned `[]` both before the first status probe
landed and after one failed, so "not asked yet" and "host says no" were the same
value. Every structured-chat launch route consumed it, and an unprobed host was
routed to legacy chat exactly as a refusing one is.
Keep the two apart: the cache holds `null` until a probe succeeds, a failed probe
leaves it `null` rather than emptying it, and the launch route names the case with
its own blocker instead of borrowing `runtime-capability`.
No routing outcome changes — both cases still decline structured chat. The point is
that the reason is now truthful, which is what the routing work needs to build on:
once a launch can target a runtime peer, capabilities come from that host, and an
unanswered remote must not be indistinguishable from one that refuses.
`hostCapabilities` on the launch route stays local-only at every call site; a
per-target resolver replaces it when the route learns to reach a peer.
* test: cover unknown runtime capability lifecycle and launch fallback
---------
Co-authored-by: Merge Sim <sim@local>
* Defer inactive browser tabs while retaining their viewport slots
Restore worktrees and tabs on demand instead of mounting the full tree.
Only render active pages and those required by automation, mobile drivers,
or remote viewers. Inactive panes stay deferred with persistent viewport
slots so their webview guests survive chrome unmounts, reducing memory
overhead when opening workspaces with many tabs.
* Defer browser pages until active and recover if evicted
Pages defer rendering until active, then retain state when inactive.
Add recovery logic to restore guests evicted by workspace memory
pressure when pages are reactivated.
* Stop retaining browser content when worktree is inactive
- Browser panes and pages now unmount when their worktree transitions to inactive, except for pages claimed by automation/mobile/viewer consumers
- Prevents unwanted restoration of all hidden browser tabs when switching between worktrees
- Tests verify proper cleanup at scale and correct page lifecycle across worktree switches
* Preserve document-preview guests when switching browser tab profiles
Document previews use a fixed partition and should not be recreated when
the profile changes. Only URL-based pages need their webviews destroyed
and rebuilt with the new profile. Includes test coverage.
* Create browser pages cold to defer guest initialization
Pages created in the background now start with loading: false, since they
don't own a guest until first shown. Only live guests can report loading
status, so background tabs sit idle until activation triggers navigation.
* Prevent document preview from swallowing pointer events during drag
Move webview registration to attachDocPreviewWebview before append,
ensuring it's enrolled in drag passthrough before becoming hittable.
When a document preview tab remounts mid-drag, the previous hook-based
enrollment landed too late. Also refactor mountEligible into
isBrowserPagePanePaintable for clarity.
* Persist agents sidebar search field visibility as pairing-local preferen
- Add `agentsShowSearch` to workspace UI state with default on
- Include in pairing-local fields so preference syncs across clients
- Convert search from menu action to checkbox menu item for explicit toggle
- Update activity thread options menu to reflect checkbox state
- Add localization strings across all supported languages
- Update RPC schemas and preference persistence layer
- Includes readiness validation reports confirming feature is clean
* rm review
* fix documentation
* feat(relay): log the region probe and name the assigned cell
A desktop silently pinned itself to a far relay region for a day and every
phone connect paid the round trip. Nothing in the desktop logs said which
regions were probed, what they measured, why one was rejected, or which cell
the host landed on, so the only way to diagnose it was a bench harness.
The resolver now emits one line per outcome. A refresh carries every region's
probe origins, the discarded warm-up, the kept samples, the minimum, the
spread, and a verdict, then the chosen region or no-hint with the reason it
withheld one. Cache hits, diagnostic overrides, and a director that cannot
list its regions each get their own line so a quiet run is never ambiguous.
Self-heal logs the cached region, the best measured region, the assigned
cell's round trip, and whether it kept or deleted the cache. Only a refresh
reports a catalog failure; a self-heal never chose a region, so a line saying
it withheld a hint would be a lie.
Relay status now carries the assigned cell so the pairing panel can name it.
The field is optional because an offline host holds no assignment and the web
client answers from a stub that never has one.
Splitting catalog fetching out of the preference module keeps both files
inside the line budget without a lint disable.
* fix(relay): drop the assigned cell from statuses not served on it
The origin pool publishes offline while it still holds the assignment it is
about to rotate, so the panel kept naming a cell nothing was served from. The
same class of bug hid a second instance: the coordinator republishes
registered right after the broker announces its cell, and that republish
carried no cell, blanking the value moments after it was set. The cell would
never have reached the panel in the real flow.
Deriving the cell from the status at each publisher removes both. The rule
lives beside the status type because it defines when the optional field is
populated, and the coordinator reads the owned broker's endpoint rather than
trusting a call site to remember to pass it.
* i18n: add the relay cell label to the English catalog
* test(relay): audit the relocated region catalog fetch call site
* fix(relay): report a self-heal whose catalog request failed instead of staying silent