Random host-local project IDs had replaced the persisted cross-host value carried by projectKey. Keep projectId for routing while reconciliation owns and persists projectKey for grouping.
Random host-local project IDs replaced the remote-derived value that had also served as grouping identity. Persist a separate opaque group key and let normal boot reconciliation backfill older records without a migration.
* feat(app): add ⌘P shortcut to switch project on New Workspace screen
Opens the existing project picker with its search focused so the project
can be switched from the keyboard (type + Enter) instead of clicking the
badge and then the project.
Wires a new "workspace.project.pick" action through the standard keyboard
pipeline (binding -> route passthrough -> dispatcher), handled by a
screen-scoped handler on the New Workspace screen that is only registered
while the screen is mounted and there are projects to pick. Because
preventDefault only fires when a handler handles the key, ⌘P/Ctrl+P still
triggers native print everywhere else. Adds a Settings -> Shortcuts help
row (rebindable) and the "Switch project" label across all locales.
* test(app): cover project picker shortcut
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(app): stop completed turns from appearing stuck
Turn activity was inferred from a user-message row flag, so a stale duplicate row could keep the working footer active after the turn ended. Track submission lifecycle separately and route every send path through the shared submission flow.
* fix(app): guard pending message identity
* test(app): measure submission layout independent of scroll
* fix(app): keep submitted messages consistent through reconnects
Track each in-flight send until its own RPC establishes acceptance, and keep canonical timeline placement authoritative. Restore legacy cached rewind IDs during cache deserialization.
* fix(app): close canonical submission races
* test(app): cover canonical submission races in browser
* fix(app): keep replacement submissions authoritative
Keep current pending rows out of legacy cache migration and leave ambiguous terminal lifecycle events to the daemon snapshot. This prevents fabricated rewind identities and stale completion events from marking replacement turns idle.
* fix(app): keep submitted messages stable across sync
Give submission transport and canonical timeline ingestion separate authority. Preserve every unresolved local send across replacement, reconcile provider identity once, and bridge RPC acceptance to authoritative running state without deriving lifecycle from timeline rows.
* fix(app): settle submissions in either acknowledgement order
Complete submission transactions when RPC and provider acknowledgement arrive in either order. Cache only transaction-owned local rows as transient data, preserve canonical ID-less prompts, and invalidate ambiguous legacy display caches instead of inventing provider identity.
* fix(app): keep agent visible during history handoff
Running and terminal updates could clear create continuity before the initial authoritative timeline arrived, leaving a streaming agent behind the loading screen. End the handoff only when authoritative history is applied.
* fix(app): settle attachment-only submissions
Canonical providers can acknowledge image-only prompts with empty text. Reconcile those events by client identity without rendering a blank canonical row.
* fix(agent): settle out-of-band message submissions
Accepted commands that do not allocate a foreground turn previously had no canonical user acknowledgement. Record the command before its handler runs so submission state and reconnect history converge through the normal timeline producer.
* fix(app): settle out-of-band submissions compatibly
* fix(app): preserve canonical prompt order
* test(app): keep workspace status check timing-independent
The workspace-status scenario asserted footer settlement before initial agent creation had necessarily completed. The dedicated draft-handoff coverage owns that lifecycle contract.
* fix(app): keep provider settings above model selector
Desktop web comboboxes used the browser top layer, so ordinary modal portals could not cover them. Keep web overlays in one relative layer stack while preserving native bottom-sheet stacking.
* fix(app): route overlay keyboard ownership
* fix(app): preserve nested overlay ownership
* fix(app): route command center overlay input
* fix(app): preserve global overlay ancestry
* fix(app): register global dialog hosts
* fix(chat): load older timeline history consistently
Pagination depended on scroll events, so short or compacted initial pages could never request older history. Reevaluate edge visibility as layout and history change, and standardize loading indicators on the canonical app spinner.
* fix(chat): keep older history loading reliably
Use authoritative timeline cursor progress so merged rows cannot stall pagination. Wait for bottom anchoring before evaluating the history edge, and re-arm retries on a fresh upward gesture without automatic failure loops.
CodeMirror uses a contenteditable surface, which the split-pane focus filter treated as exempt. Let editor interactions claim pane focus just like composer text inputs do.
* fix: prevent Shift+Tab from changing a backgrounded agent's mode
The New Workspace mode-cycle shortcut (Shift+Tab) could reach a
still-mounted background agent's mode control and silently change that
running agent's execution mode — including into a permissive/bypass
mode — with no feedback in the New Workspace UI.
Root cause is in useKeyboardActionHandler: it re-registered its handler
on every render (a fresh inline `actions` array plus a changing `handle`
identity). The dispatcher runs the most-recently-registered matching
handler first, so a frequently re-rendering control climbed ahead of the
active one and consumed the key. The registered `handle`/`enabled` were
also captured per registration and read stale by the native keydown
listener.
Fix: register once per (handlerId, priority, actions); read `handle` and
`isActive` live from a ref; fold `enabled` into a fresh `isActive` so
the dispatcher re-checks it at dispatch time. Registration order stays
stable and callbacks stay current for all six consumers of the hook.
Add an e2e regression test that opens a live agent, opens New
Workspace, and presses Shift+Tab. It asserts the running agent's mode
is unchanged: its daemon-committed mode, plus no set_agent_mode_request
on the wire. It fails on the previous code (the mode was flipped to a
more permissive mode) and passes with this change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(app): restore workspace pin shortcut build
The shortcut change landed with an import path removed by the workspace tabs reshape, breaking app typecheck and web builds. Import the canonical workspace tab model.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Cmd+Shift+P did nothing whenever the active workspace's sidebar row was
not rendered. The `workspace.pin` handler was registered by the row
itself, gated on `selected && canPin`, so collapsing the row's section
unmounted the only handler for the action and the dispatcher found zero
candidates. The keypress was swallowed with no toast and no error.
Move the action to a single always-mounted handler keyed on the active
route selection, following the existing `useGlobalNewWorkspaceAction` /
`useActiveWorktreeNewAction` pattern, and delete the two per-row
registrations. Besides the reported case this also fixes a collapsed
status group, a collapsed Pinned section (so unpinning works too), and
focus mode.
The handler lives in a headless component rather than being called from
the root layout, so subscribing to the active workspace's pin state does
not re-render the whole app shell.
Two supporting changes:
- The controller now takes a narrow `PinnableWorkspace` instead of a
full `SidebarWorkspaceEntry`, so a caller without a sidebar row can
build one. Its in-flight guard moves to module scope, because the row
menus and the shortcut hold separate controller instances and a
per-instance guard would let a keypress and a menu click fire two
concurrent, opposite RPCs.
- The handler resolves the descriptor id via `useWorkspaceFields` rather
than reusing the route id. The route carries an opaque workspace id
that is not guaranteed to equal the descriptor id, which is why
`selectWorkspace` resolves it through
`resolveWorkspaceMapKeyByIdentity`. Both the RPC and the in-flight key
need the descriptor id so that rows and this handler agree on one
identity.
`workspace.archive` has the same row-scoped defect and is deliberately
left alone: it carries per-row state (`isArchiving`, optimistic hiding,
the risky-worktree confirm) and needs its own change.
Covered by a Playwright spec: the three collapse cases fail without this
fix, plus one-RPC-per-press and a rejected-pin case that asserts the
error toast and that the next press still succeeds.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Searching the command center for a branch name like "master" returned a
Workspaces list where every row looked identical: the title was the
branch and the subtitle read "<hostname> · <branch>". On a single-host
machine the hostname repeats on every row, so there was no way to tell
which project each "master" workspace belonged to.
Add the project name to the workspace subtitle (host · project · branch)
and gate the host label behind multi-host, matching the Agents section.
The host is dropped on single-host setups, so the subtitle reads
"project · branch" and rows are distinguishable. Because searchText is
derived from the subtitle, typing a project name now matches its
workspaces too.
Extract the shared join into joinSubtitleParts() and route both the
workspace and agent subtitles through it, removing the duplicated
filter/join the Agents section hand-rolled.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Retained group headings could enter loading after their initial layout, leaving React Native Web without a registered layout observer and the shimmer with a zero-length endpoint. Measure web badge labels from mount so later loading states reuse real dimensions.
* fix(app): preserve file line endings and UTF-8 BOM
* refactor(app): simplify line ending preservation
Let the editor parse newline variants and serialize with the file's first separator. Mixed-ending files become uniform on edit without a separate normalization layer.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(app): refine compact composer controls and native scrolling
Consolidate responsive agent controls and model browsing inside the compact sheet. Preserve manual native scrolling by suspending sticky bottom maintenance while the user owns the viewport.
* fix(app): suppress recoverable resume refresh errors
Resume revalidation can race host reconnection and reject while the host is offline. Treat the refresh as deferred so development LogBox does not surface a recoverable disconnect.
* fix(app): stabilize compact model sheet loading
Let the model list consume the sheet space above persistent controls as the sheet expands. Keep unresolved defaults in a loading state so Select model only represents a genuinely empty selection.
* fix(app): preserve route anchors during native scroll
* fix(app): preserve native scroll intent and sheet dismissal
* fix(app): keep compact sheet styles composable
* fix(app): open notifications on the right host
Agent notifications previously omitted workspace ownership, so a cold target host was treated as missing and fell back to its empty home route. Carry the authoritative workspace and keep older notifications on a target-host resolver until lookup is conclusive.
* fix(app): separate notification and agent URL routing
Notifications use their authoritative workspace target directly. Stable agent URLs remain server-and-agent targets whose workspace is resolved by the agent route.
* fix(protocol): preserve notification workspace targets
Both attention message variants retain workspaceId through outbound validation so notification clicks receive the authoritative route target.
* test(app): use workspace-scoped notification target
* test(server): give notification agents workspace targets
* test(app): target notification workspace in navigation e2e
Open the complete working comparison in a desktop workspace tab while preserving inline sidebar diffs when the tab is closed.
Working and commit comparisons share one diff panel, and workspace layout is the sole owner of live tab persistence and identity.
Refs #1520
* fix(app): restore archived agents from History
History navigation lost the explicit archived-agent intent during tab reconciliation, and workspace recovery stopped after restoring the workspace. Preserve the selected tab and recover its provider session as one action while serializing provider resume with timeline hydration.
* fix(app): preserve archived agent recovery invariants
* fix(server): preserve timeline hydration call shape
* fix(app): retain agent tabs through lookup
* fix(server): upgrade in-flight timeline broadcast
* fix(app): open chat file links at referenced lines
Pass chat file locations through the editable CodeMirror path so opening a reference selects and scrolls to its requested line. Cover the real assistant-link flow with a browser regression.
* fix(app): repeat chat file navigation
Track file navigation separately from stable tab identity so reopening the same path and line recenters both editable and read-only panes. Extend the browser regression to cover moving away and clicking the same link again.
* test(app): stabilize repeated file link navigation
The web UI font rule only targeted the app root, so portaled content fell back to the default font. Extend the rule to the shared overlay root and cover it with a browser regression test.
Visibility catch-up could select the blocking overlay after optimistic continuity cleared, even though history was already hydrated. Keep hydrated content non-blocking and cover the creation handoff with a browser regression test.
* fix(workspaces): start pasted pull requests from their branch
Recognized pull request links now select their branch directly instead of
waiting for a separate confirmation. Ensure single-branch clones also record
the fetched branch so the new worktree can track it.
* fix(workspaces): keep pasted PR checkout target-safe
* fix(workspaces): make pasted PR resolution deterministic
* fix(workspaces): preserve explicit checkout selection
A recognized pull request selects itself only when its attachment is added. Once the user chooses a branch, later composer edits must not derive checkout state from existing attachments.
* fix(composer): ignore stale pull request lookups
A lookup may finish after the workspace target changes. Only apply its attachment and selection event when the host, working directory, client, and remote still match the target that started it.
* fix(workspaces): make checkout choices authoritative
Arm automatic pull request selection only when a new PR link is detected. An explicit picker choice cancels that pending selection, existing draft attachments never infer checkout state, and target changes suppress carried-over lookups.
* fix(workspaces): stabilize pull request auto-selection
Keep the first PR selected when one edit contains several links, and accept a completed lookup when only the transport client changed for the same host and checkout.
* fix(composer): release removed pull request lookups
Release each auto-attach invocation when its composer state is abandoned, while continuing to discard any stale response.
* feat(files): edit workspace files on web
Keep source buffers synchronized with host file changes and require an explicit overwrite or reload when revisions diverge.
* feat(panels): surface and protect modified tabs
Expose tooltip and modification state through the generic panel boundary so tabs can show stable metadata and guard every close route consistently.
* fix(tests): use portable fake timeout handle
* fix(files): harden editor conflict handling
Preserve modified panel state across tab eviction, use precise revisions for optimistic writes, coalesce concurrent file watchers, and localize the editor interface.
* fix(files): close editor concurrency gaps
Coalesce clean reloads, preserve subscriber identities and file permissions, suspend pending saves during close confirmation, and carry precise revisions through file reads.
* test(files): expect read revision metadata
* fix(app): keep sidebar pins visible while reopening
Hidden sidebars stop consuming live workspace updates but retain their last rendered entries so the opening animation never exposes an empty list.
* test(app): reuse sidebar pin flow
* fix(app): keep focused agent timelines live
Window focus was conflated with app visibility, so switching OS windows could remove the selected timeline subscription. Separate those signals, grace every visibility-driven removal, and cover retained history until authoritative catch-up completes.
* fix(app): keep timeline catch-up failures non-blocking
* fix(app): surface timeline catch-up failures
* fix(app): preserve file preview refocus
* fix(app): preserve optimistic catch-up flow
* fix(projects): detect when projects become Git repositories
Project identity was coupled to Git placement, while non-Git roots were dropped by session-scoped observation. Keep identity tied to the selected root and observe Git transitions daemon-wide so empty projects update without rehoming workspaces.
* fix(projects): preserve metadata across Git read failures
Refresh archived workspace facts before persistence and treat only confirmed non-repositories as non-Git so transient failures cannot rewrite stored project metadata.
* fix(projects): close project update races
* fix(projects): refresh checkout metadata when reopening folders
Persist worktree ownership separately from workspace kind and gate exact-root project creation on stable host identity. Refresh active and archived records so missed Git transitions cannot return stale checkout descriptors.
* test(projects): accept Windows aliases for repaired roots
* test(server): retry transient Windows cleanup locks
* fix(projects): propagate project metadata refreshes
Project-only Git transitions now fan out workspace updates for legacy clients. Explicit project creation refreshes stored kind, and equivalent cwd spellings reuse existing workspace records.
* fix(projects): refresh worktree source project kind
* fix(projects): preserve exact-folder runtime isolation
* fix(projects): preserve exact cwd across worktree lifecycle
* fix(projects): harden exact-folder worktree flows
* fix(projects): derive exact cwd from matched path identity
* fix(projects): preserve nested worktree lifecycle
* refactor(projects): centralize workspace placement
* test(e2e): verify isolated server ports
* fix(projects): preserve placement reshape compatibility
* fix(projects): validate created worktree placement
* fix(projects): preserve placement through workspace lifecycle
Archive and recovery were rediscovering or guessing checkout roots instead of consuming the persisted workspace placement. Make the record authoritative for exact cwd, backing worktree, and source repository, and classify stale paths before Git reconciliation.
* fix(worktrees): preserve source checkout root
Convert Git's common administrative directory back to the source checkout root when legacy archive placement is reconstructed.
* fix(worktrees): compare filesystem identities
Resolve discovered worktrees and teardown locations through the shared realpath-aware matcher so Windows short and long path spellings cannot split placement identity.
* fix(worktrees): centralize path containment
Route worktree ownership, listing, resolution, and deletion through the realpath-aware containment primitive so Windows path aliases cannot be filtered by an earlier raw prefix check.
* fix(worktrees): remove duplicate ownership discovery
* test(e2e): isolate daemon restart ownership
* fix(worktrees): make lifecycle operations transactional
* fix(workspaces): share git watches by cwd
* test(worktrees): make teardown proof portable
* fix(sync): integrate project updates with directory owner
* fix(workspaces): close reconciliation edge cases
* refactor(sync): remove duplicate project reconciliation
Keep workspace and project deltas behind the host directory transaction, with owner-boundary coverage for snapshot replay and queued full reconciliation.
* fix(sync): buffer project updates before hydration
Start the workspace transaction with the online connection epoch so project broadcasts cannot publish a partial directory before the authoritative snapshot commits.
* feat(sync): keep live data scoped and current
Only viewed chats receive live timeline rows, while directory state now uses one subscribed bootstrap followed by ordered deltas. Legacy clients and daemons retain their existing behavior through centralized compatibility gates.
* fix(sync): preserve selective delivery boundaries
Keep selective timeline capability app-owned, union viewed sets across shared sockets, and reconcile directory side effects from accepted state. Visibility and archive suppression now follow their existing authoritative boundaries.
* test(app): align browser fixtures with runtime contracts
* fix(sync): preserve mixed-client delivery guarantees
* fix(sync): finish paged history before advancing
* fix(sync): replay live deltas after refresh failures
* test(server): model socket capability lookup
* test(app): use platform shortcut for split-pane coverage
* fix(app): keep hidden timelines dormant
* test(app): normalize split-pane shortcut setup
* fix(app): preserve sync state across retries
* fix(app): preserve background sync state across races
* fix directory bootstrap reconciliation edge cases
* fix selective timeline compatibility races
* fix superseded directory sync races
* fix(sync): centralize directory replica ordering
Own agent and workspace refresh transactions in HostRuntime so reconnect epochs, buffered updates, and lifecycle invalidation share one ordering boundary. Route timeline metadata and mixed-capability stream delivery through their authoritative runtime sources.
* fix(app): keep timeline requests runtime-scoped
* fix(app): close directory bootstrap races
Mouse input inherited the touch hold delay. Split activation by input so mouse dragging begins after deliberate movement while touch retains long-press arbitration.
* fix(terminal): retry size claims until sent
A focus resize could consume its once-per-focus latch before the renderer handed the size to the client. Keep the claim pending across visibility, connection, and renderer readiness changes, and only commit it after the resize is sent.
* fix(terminal): keep disconnected claims retryable
A retained runtime client can outlive its active connection. Require the connection at delivery time so a delayed resize callback cannot commit a claim that was never sent.
* test(terminal): await PTY resize observation
Prove the terminal starts at 80x24 while blurred, then probe through the daemon until its own reported size matches xterm after focus returns. This avoids racing a one-shot stty sample against the asynchronous resize claim.
* fix(workspace): keep focus mode scoped and easy to exit
Route focus mode through the active workspace so persisted state cannot hide chrome on settings or other screens. Add a visible exit control and keep desktop window chrome aligned and visually quiet.
* fix(app): clarify muted chrome semantics
Pi and OMP share only the JSONL child-process transport while retaining provider-owned launch, RPC, runtime, session, history, and permission behavior. Removes captured fixtures in favor of typed harnesses and real-provider coverage.
Closes#2006Closes#2060
* fix(app): restore missing workspaces from History
Workspace and agent archival are separate lifecycles. Carry restore intent from History so closed agents can reopen archived workspaces without changing ordinary navigation.
* fix(app): wait for workspace hydration before restore
* fix(app): preserve History agent unarchive
* fix(app): reopen archived History agents outside active directory
History entries are not merged into the active session maps. Carry the row archive state through explicit restore intent so live workspaces still reopen without broadening ordinary navigation.
* fix(app): ignore stale History archive state for active agents
A cached History row can outlive a successful reopen. When the workspace exists, live session state now wins so an active agent is not interrupted by another refresh.
* fix(app): reopen every selected archived History agent
History entries without workspace IDs returned before restoration, and a second archived agent sharing an in-flight workspace restore was dropped. Preserve both agent-only and deferred reopen intent.
* fix(app): require explicit workspace recovery
* fix: preserve workspace recovery contracts
* fix: preserve workspace recovery compatibility
* fix: preserve recovered workspace session state
* fix: preserve terminal navigation timing
* fix(server): preserve successful workspace recovery
* fix: preserve workspace recovery intent
* feat(providers): remove custom providers from settings
Add a destructive removal flow so mistaken custom providers can be deleted from config.json instead of only disabled.
* test(app): cover provider removal with e2e
Move provider removal coverage out of mocked component tests and into the real Settings flow.
* fix(providers): keep removal live after config updates
* fix(settings): show daemon update failures
React Native web ignores Alert.alert, so failed remote updates reset to an enabled button without any user-visible explanation. Keep updater progress and failure as explicit rendered state, including the daemon's error, and cover the real browser-to-daemon failure path.
* test(settings): harden daemon update regression
* fix(settings): disable Desktop-managed daemon updates
Desktop-bundled daemons advertised npm self-update even though the install-origin checks always rejected it. Report Desktop ownership to clients, render actionable guidance, and refuse stale requests before touching npm.