* feat: allow custom worktree branch names
* refactor(ui): render custom branch name field unconditionally under advanced container
* fix(composer): hide manual branch field when a work-item source drives the branch
A tracked PR/issue/MR/Linear source derives the branch itself, and a linked
GitHub PR re-resolves the branch name at submit — so an override typed in the
Advanced branch field was silently ignored. Only render the field for the
typed-name and base-branch flows, where the manual override is honored.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): avoid SF Mono fallback on iOS terminal
* test(mobile): cover touch iPadOS terminal font fallback
* refactor(mobile): share terminal font fallback tail across platforms
Dedup the identical fallback chain that the iOS/non-iOS branches each
repeated so the two platforms can only differ in the lead family and
cannot silently drift. Make the regression tests behavioral: assert the
resolved chain always terminates in the generic monospace (the real iOS
bug) and that both platforms share an identical tail.
Co-authored-by: Orca <help@stably.ai>
* test(mobile): anchor font-block extraction on font markers only
The VM-slice end boundary was an unrelated text-scale comment; re-anchor
it on the terminalFontFamily declaration so edits below the font block
cannot break the extraction.
Co-authored-by: Orca <help@stably.ai>
* chore(mobile): bump terminal-webview-html max-lines ratchet to match file size
The iOS-safe font selection block adds a few code lines to
terminal-webview-html.ts, pushing it to 1784. Bump the grandfathered
per-file ratchet to match, consistent with prior ratchet bumps.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* feat: 모바일 터미널 한글 미러 스텝 순수 모델 추가
* feat: 미러 델타 순서 보장용 send 체인 추가
* fix: 모바일 터미널 한글 입력을 미러 모델로 전환
* fix: 탭 상태 지연 중 한글 조합 상태 소실 방지
* fix: 미러 가드와 send 체인 리뷰 지적사항 반영
탭 상태 지연으로 활성 탭 타입이 일시적으로 null이 될 때 runMirrorStep의 stale-handle 가드가 조합 중 음절을 버리지 않도록 pending-clear 효과와 동일한 null 허용 패턴 적용. 테스트 하네스가 ref와 prop을 동일 소스에서 파생하도록 결합해 실제 경로의 lag 프레임을 검증. queueTerminalLiveMirrorSend의 previousSend await를 catch로 보호.
* refactor(mobile): drop dead queueTerminalLivePendingFlush orphaned by the mirror model
The mirror model migrated all live-input sends to queueTerminalLiveMirrorSend,
leaving queueTerminalLivePendingFlush referenced only by its own tests. Remove
the dead function and its three tests.
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): expose live terminal keyboard target
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): refocus live keyboard after dismissal
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: realitsyourman <wongil@demodev.io>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
The POSIX agent-hook script for every curl-based agent inlined the full
event payload via `curl --data-urlencode "payload=${payload}"`. Tool
output can be tens of KB, so the resulting process command line could be
multi-KB — which endpoint security tools (e.g. Microsoft Defender for
Endpoint) flag as an oversized/suspicious command line. That produced a
false-positive detection on Orca's own loopback (127.0.0.1) telemetry POST.
Stream the payload to an mktemp file and post it with
`--data-urlencode "payload@$payload_file"` instead. The urlencoded body on
the wire is byte-identical, so the agent-hook receiver is unchanged; the
payload simply never appears on a process command line. `trap ... EXIT`
removes the temp file on every exit path. Small bounded metadata fields
(paneKey/tabId/worktreeId/env/version) stay inline.
Applied to all curl-based agents: claude, codex, command-code, copilot,
cursor, droid, gemini, grok, antigravity. (amp/hermes/opencode post via
the HTTP request body and were never affected.) The Windows post-command
shares the same latent pattern and is flagged as follow-up.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(gitlab): port-aware self-hosted host recognition
Use the URL host (including a non-default web/API port) as the GitLab
host identity instead of the port-less hostname, and match known hosts
port-aware:
- A known-host entry without a port matches any port of the same
hostname (preserves legacy bare-host and gitlab.com recognition).
- A known-host entry WITH a port matches only that exact host:port, so
two services sharing a hostname on different ports (e.g. a GitLab and
a Gitea) are no longer conflated.
- For ssh/git remotes the port is a transport port (e.g. ssh :2222) and
is dropped; for http(s) remotes the port is the endpoint and kept.
- Also capture an optional :port in parseGlabAuthStatusHosts so a
self-hosted GitLab on a non-default port is discovered correctly.
* fix(gitlab): per-connection known-hosts cache + port-aware auth-status parsing
getGlabKnownHosts() was connection-blind and cached process-globally,
and on any failure it cached [gitlab.com] forever — so a repo on an SSH
connection never discovered its self-hosted host once a probe failed
before the tunnel was ready.
- getGlabKnownHosts(connectionId?) now caches per connection so a
connected repo's authenticated hosts don't leak into the local
context (or vice versa).
- The failure fallback (canonical default) is no longer cached, so a
later probe can re-discover the real host once auth/tunnel is ready.
- parseGlabAuthStatusHosts captures an optional :port on both the
'Logged in to <host>' and header-style lines, keeping two services on
the same hostname distinct by port.
* fix(gitlab): isolate unresolvable projects instead of cwd-fallback that hits exit 128
listIssues/getIssue fell back to an unscoped 'glab issue list' / 'glab
issue view' that infers the project from cwd. For a repo on an SSH
connection cwd is not the repo dir, so glab runs git resolution in a
non-repo dir and fails with 'git: exit status 128'. In an 'All projects'
aggregate one such failure could sink the whole issues panel.
When a projectRef cannot be resolved, return a structured, isolated
per-project result (listIssues: { items: [], error: not_found };
getIssue: null) and spawn no glab subprocess. Behavior is unchanged when
a projectRef IS resolved (the scoped '-R' / 'api projects/...' path).
* fix(gitlab): recognize modern /-/work_items/<iid> issue URLs
Modern GitLab emits issue URLs as /-/work_items/<iid> in addition to the
legacy /-/issues/<iid>. The URL classifiers only matched /-/issues/, so
work-item-form issue links went unrecognized.
Extend the gitlab-links parsers (parseGitLabIssueOrMRNumber /
parseGitLabIssueOrMRLink, which also backs isWorkItemLookupText) and
isGitLabIssueUrl to accept /-/work_items/<iid>, mapping it to an issue
work item with the same project-path + iid extraction.
* fix(gitlab): thread connectionId into getGlabKnownHosts call sites
Follow the existing connectionId-threading pattern: pass the repo's
connectionId into every getGlabKnownHosts() call (client.ts,
work-item-details.ts, orca-runtime.ts) so the per-connection known-hosts
cache is keyed correctly and self-hosted hosts are discovered against
the right glab context.
* docs(gitlab): use generic example hosts in comments
* fix(gitlab): pass self-hosted host:port via GITLAB_HOST (glab --hostname rejects ports)
* polish: satisfy oxlint curly + oxfmt on merged gitlab port-recognition code
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Ptah-CT <auctor@xinfty.space>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): bundle terminal engine and show load errors instead of a blank pane
The mobile terminal WebView loaded xterm.js from cdn.jsdelivr.net at
runtime; old WebViews (< Chrome 85) fail to parse the modern bundle and
blocked-CDN networks fail to fetch it, and the resulting error was
silently dropped, leaving the pane permanently blank (#7030).
Bundle the engine into the app via exact-pinned npm deps + a postinstall
esbuild step (chrome74 target, guarded WeakRef/structuredClone/
replaceChildren shims) emitting a gitignored generated module, inline it
into the terminal document, and surface fatal engine failures as a
visible overlay with diagnostics and a Reload wired into the existing
resubscribe path. Non-fatal errors log without covering a live terminal.
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): add a native watchdog so a dead terminal document can't stay silently blank
CodeRabbit round: if the webview document dies before the glue can post
anything (or the RN message bridge never comes up), no error message and
no native handler fires. Arm a 15s foreground-gated watchdog per document
generation that paints the fatal overlay when web-ready never arrives;
first fatal diagnostics win over later cascades. Extract the watchdog and
the public contract types to keep TerminalWebView under the line cap, and
document the SVG xmlns percent-encoding transform.
Co-authored-by: Orca <help@stably.ai>
* test(mobile): unmount TerminalWebView renderers so watchdog timers can't leak across tests
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Fixes#5579. Pass previous_tag_name to GitHub generate-notes so draft-release notes stop accumulating across releases; omit it safely on first release. Adds tag parse/compare + paginated tag fetch and regression tests.
* fix(serve): install the orca CLI in headless serve mode
In headless serve mode the orca CLI command (~/.local/bin/orca on Linux,
/usr/local/bin/orca on macOS) is never created, so the in-app Claude Team
launcher (orca claude-teams) opened by 'Start new agent' fails with
command-not-found. CliInstaller.install() is only invoked from renderer/desktop
flows (onboarding, Settings 'Install CLI', feature tips) via the cli:install IPC;
headless serve has no renderer. Run the existing idempotent installer on the
serve startup path, best-effort so a failure never blocks serve startup.
Fixes#5763.
* fix(serve): install a bare `orca` dispatcher on Linux for Claude Team
On Linux CliInstaller installs the CLI as `orca-ide` (to avoid shadowing GNOME
Orca's /usr/bin/orca), not bare `orca`. But the Claude Team launcher (`orca
claude-teams`), composed client-side by both the desktop renderer and the
mobile app and written verbatim to the host PTY, invokes bare `orca` — which
is unresolved in the initial managed terminal (no agent-teams shim on PATH
yet). The serve-mode CLI install therefore fixed macOS but not Linux.
Add a bare-`orca` dispatcher in the serve branch (host-side, Linux only) that
execs the bundled CLI wrapper. ~/.local/bin is hardcoded ahead of /usr/bin on
the managed-terminal PATH, so it resolves. Plain file (not a managed symlink)
so CliInstaller.removeLegacyLinuxCommandIfManaged leaves it untouched.
* Restore ai-vault files to origin/main (drop stale pre-0.52 oxfmt merge artifact)
Co-authored-by: Orca <help@stably.ai>
* Harden serve CLI install: AppImage-safe dispatcher, no-prompt macOS, skip Windows, conflict guard
- Gate CliInstaller.install() to macOS+Linux; Windows would only mutate the
persistent user-registry PATH without helping the current serve's terminals.
- Inject a non-prompting privilegedRunner so headless macOS never pops an
osascript admin dialog when /usr/local/bin isn't writable (skips instead).
- Linux dispatcher: exec the stable $APPIMAGE via buildAppImageCliWrapper when
running from an AppImage (resourcesPath is an ephemeral FUSE mount); skip when
the bundled orca-ide launcher is absent; never clobber a user-owned ~/.local/bin/orca.
- Single-quote the exec target; add coverage for all new branches.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Daniel Rudaev <daniel@d1dx.com>
Co-authored-by: Orca <help@stably.ai>
* fix(browser): restore programmatic focus for CDP text insertion
* test(browser): cover the DOM focus replay error path
* fix(browser): serialize DOM.focus replay against pipelined Input.insertText
* refactor(browser): consolidate pending-focus invalidation into one guard
Collapse the three per-handler deletes (Page.bringToFront, Page.captureScreenshot,
catch-all) of the pending DOM.focus replay into a single guarded delete keyed on
'not DOM.focus and not Input.insertText'. States the invariant in one place and
removes the trap where a future early-returning handler forgets to clear the
stale focus. Also clear the map on client teardown and shorten the field comment.
Co-authored-by: Orca <help@stably.ai>
* test(browser): annotate mock webContents return type for portable declaration emit
Co-authored-by: Orca <help@stably.ai>
* fix(browser): drop pending focus replay when the client disconnects mid-flight
Co-authored-by: Orca <help@stably.ai>
* fix(browser): re-check active client after DOM.focus replay round-trip
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* Add terminal link browser context action
* Polish terminal link default-browser action
- Route Open in Default Browser through the shared openHttpLink funnel
(forceSystemBrowser) so loopback dev-server links get the same
worktree-label rewrite as shift+click's system-browser path.
- Translate the new menu label in es/ja/ko/zh (was en-only).
- Extract TerminalQuickCommandMenuItem to keep TerminalContextMenu under
the max-lines lint after adding the link item.
- Hit-test the link only when the menu actually opens (skip on Windows
right-click copy/paste path).
- Add scheme-safety unit tests (file:/javascript:/smb:/vscode: rejected)
and a menu-item render/callback test; fix TerminalContextMenu.test
props for the new required fields.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(browser): keep isolated profile storage on its partition
* fix(browser): keep cloned isolated tabs on their resolved partition
* fix(browser): thread resolved partition through UI profile switches
Keep the isolated-storage invariant universal: UI-initiated profile
switch/create now persist the resolved partition alongside the profile
id, matching the runtime path, so a tab stays on its partition even if
the renderer profile mirror is later stale (issue #6923).
Co-authored-by: Orca <help@stably.ai>
* fix(browser): persist resolved partition through session restore
Add sessionPartition to the workspace-session zod schema so the resolved
partition survives persist->load; without it zod strips the field and a
restored isolated tab whose profile mirror is stale at startup falls back
to the shared default partition, reopening the storage leak (#6923).
Fold the webview teardown branches (parent drift or partition mismatch)
into one and re-resolve the viewport container once, matching the
pre-refactor null-guard behavior.
Split the sleeping-agent parse tests into their own file to keep both
under the 800-line cap.
Co-authored-by: Orca <help@stably.ai>
* fix(browser): keep CLI-created tabs inheriting the default profile
browserTabCreate without an explicit profile was sending sessionProfileId:
null, which the renderer store treats as 'no inheritance' (its guard is
!== undefined). That forced CLI-created tabs onto the shared default
partition even when the user had configured a default browser session
profile, silently changing behavior a #6923 fix should not touch.
Leave sessionProfileId/sessionPartition undefined when no profile is named
so the renderer applies default-profile inheritance; thread the resolved
partition only when a profile is explicitly chosen.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix: drop stale repos fetches so deleted projects don't reappear
Deleting a project group with "Remove contained projects" could leave the
removed projects as stale, unusable sidebar rows until an app restart (#7020).
`repos:changed` fires once per removal and the renderer starts an unsequenced
repos fetch per event; an earlier fetch that read pre-removal state could
resolve last and overwrite the newer result, reintroducing the deleted repos.
Guard fetchRepos and fetchReposForAllHosts with a monotonic token so a fetch
drops its own result once a newer repos fetch has superseded it — only the
latest fetch, which reads the final persisted state, applies. Add a regression
test that a stale fetch resolving after a newer one can't resurrect a removed
repo.
* test: isolate stale-fetch race in a dedicated file; scope guard to fetchRepos
Move the #7020 regression test out of repos.test.ts into a focused
repos-stale-fetch.test.ts and add a reject-path case (a superseding fetch
that later rejects must still block the older stale fetch). Scope the
monotonic guard to fetchRepos only: the original shared-counter guard on
fetchReposForAllHosts let an unrelated fetchRepos bump the counter and
abort an in-flight all-host load, dropping every host's repos.
Co-authored-by: Orca <help@stably.ai>
* chore: restore origin/main cdp-ws-proxy.test.ts (drop merge artifact)
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix: allow empty nested import as folder
* Track open-as-folder recovery action and localize its strings
Completes the empty-nested-import fallback: adds the open_as_folder action to the existing add_repo_nested_import_action funnel (integer counts only, no paths) so adoption of the recovery path is measurable, and lands the five-locale catalog entries the new UI strings require.
Co-authored-by: Orca <help@stably.ai>
* Pin runtime-kind mock return to the literal union in nested import flow test
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* Fix CDP printToPDF proxy
* Harden CDP PDF stream handling
* Fix PDF margin units
* refactor(cdp): extract printToPDF param mapping + stream store into cdp-print-to-pdf
Move the CDP Page.printToPDF option translation and the ReturnAsStream handle
store out of cdp-ws-proxy into a focused cdp-print-to-pdf module, mirroring the
existing cdp-screenshot sibling. Keeps the over-budget proxy from growing
further and gives the PDF logic direct unit coverage.
No behavior change: all existing proxy tests pass unmodified.
Co-authored-by: Orca <help@stably.ai>
* fix(cdp): drop print result if client disconnected during printToPDF
A ReturnAsStream print that resolved after the requesting client
disconnected (or was replaced) registered its buffer in pdfStreams after
the disconnect's clear() already ran, orphaning a potentially large PDF
until the 5-minute TTL. Re-check isActiveClient after the await, mirroring
navigateWithLifecycle/reloadWithLifecycle.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <foxtrot-eng@stably.ai>
Co-authored-by: Orca <help@stably.ai>
* Allow arbitrary hostnames in manual network address entry
parseManualNetworkAddress only accepted an IPv4 address or a Tailscale
MagicDNS (*.ts.net) hostname, so users behind a dynamic residential IP
who rely on a DDNS domain or self-hosted relay had no way to enter it
in the desktop UI short of bypassing validation via DevTools/IPC.
The main process already resolves any host: resolvePairingEndpoint
and parsePairingAddressOverride in src/main/runtime/runtime-rpc.ts
accept an arbitrary hostname and an optional host:port. This change
brings the renderer-side validation in line with what pairing already
supports: any RFC 1123 hostname (a superset that still covers
*.ts.net), optionally suffixed with :port (1-65535). IPv4 validation
is unchanged, including still rejecting malformed dotted-numeric input
instead of silently treating it as an all-digit hostname.
Updates the custom-address dialog copy in NetworkInterfacePicker.tsx
to describe the wider grammar.
* Polish manual-address takeover: fix bare-numeric guard, sync 5 locales, lint
- Require a dot in the IPv4-typo guard so a bare numeric label (`123`)
validates as a legal RFC 1123 hostname, matching the code's own comment
and the main-process resolver; add coverage.
- Update en.json + es/ja/ko/zh placeholder/hint to the broadened grammar
(translate() reads en.json before the TSX fallback, so the copy change
was previously inert; the other locales described the old ts.net-only rule).
- Replace indexOf(...)!==-1 with includes() to satisfy oxlint.
Co-authored-by: Orca <help@stably.ai>
* Keep validator a strict subset of the backend resolver
Review surfaced two ways the renderer could accept an address the main
process handles differently:
- All-numeric hosts (bare `123` and dotted `256.0.0.1`) are now rejected.
The WHATWG URL host parser downstream reinterprets a numeric host as IPv4
(`123` -> `0.0.0.123`), so accepting one would validate an address the
pairing resolver silently dials as a different host.
- Ports with leading zeros are rejected. `^[0-9]+$` let an arbitrarily long
zero-padded string past the range check and inflate the returned address
beyond the hostname length cap that the old whole-string check enforced.
Co-authored-by: Orca <help@stably.ai>
* Reject any numeric final label, not just fully-numeric hosts
WHATWG URL host parsing treats a host whose last label is numeric
(`foo.123`, `foo.0x1`) as an IPv4 signal, so the pairing resolver would
fail to parse it and silently dial a fallback host. Widen the ambiguous-IP
guard to a single last-label check that subsumes the earlier all-numeric
case, keeping the renderer a strict subset of what the backend resolves
correctly. Normal hostnames whose last label merely contains digits
(`host2.example.com`) are unaffected.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(sidebar): keep independent same-host checkouts of one repo as separate projects
Re-regression of #5374: PR #6320 removed the per-host-surface separation
added by #5375, so independent local clones of the same repo (shared origin
=> same github:owner/repo identity) collapse into one sidebar project again.
Restore the surface-key separation in worktree-list-groups.ts, scoped by
ProjectHostSetup.setupMethod: 'provisioned' (ephemeral-VM runtime copies)
still nest under the project header (preserving #6320), while real user
checkouts (legacy-repo / imported-existing-folder / cloned) each get their
own header when duplicated on one host surface.
Revert the two tests #6320 inverted and add coverage for a provisioned copy
nesting alongside a same-host user checkout.
* test(cmd-j): update palette grouping tests for same-host checkout split
The Cmd+J palette shares getProjectGroupingForRepo (via
getProjectHeaderRevealTarget) with the sidebar, so restoring same-host
checkout separation also splits independent `cloned` checkouts into
per-setup palette entries. Update the multi-setup test to assert the split
and add coverage that a `provisioned` runtime copy still nests under one
project key.
* test(sidebar): cover split + provisioned-nest on one host together
Guard the intersection of #5374 and #6320: when a project has two real
same-host user checkouts AND a provisioned copy, the real checkouts each get
their own per-setup header while the provisioned copy stays under the plain
project key (never a path-scoped ::setup: header). Locks the two features'
combined behavior so a future change can't silently regress either half.
* refactor(sidebar): reuse shared Windows-path helper, single-pass surface count
Take-over polish of #7237:
- Use shared isWindowsAbsolutePathLike instead of an inline drive-letter
regex, deduplicating the pattern and also bucketing UNC \\server\share
paths as a Windows surface instead of falling through to default.
- Collapse buildProjectGroupingIndex's two passes over projectHostSetups into
one, computing each surface key once and flagging ambiguous projects from
the aggregated counts.
- Tighten why-comments to AGENTS.md's two-line rule.
No behavior change to the fix; all 123 sidebar/palette grouping tests pass.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix: block resuming host-stored AI Vault sessions into SSH workspaces
AI Vault scans session history from this machine's disk only (host home
dirs plus local WSL homes), but since #6685 resume actions accepted any
SSH workspace as a target. Resuming a host-stored session there queues
cd '<local cwd>' && <agent> --resume <id> into a remote shell where
neither the cwd nor the session file exists, so it always fails.
Gate resume targets on where the session file lives: local targets stay
unrestricted, SSH targets now require a WSL-stored session (the
SSH-to-local-WSL setup #6685 was built for). Applies to the sidebar
resume buttons, direct resume, and pane drag-and-drop, with a toast
explaining the host mismatch. Catalog sync also picked up two
pre-existing drifts (missing failedUnnestWorkspace fallback, two
unreferenced SSH-timeout keys).
* i18n: translate host-session SSH-resume block message (es/ja/ko/zh)
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* test(agent-completion): cover done after waiting pause
Co-authored-by: Orca <help@stably.ai>
* test(agent-completion): assert attention fires alongside done-after-waiting
Wire dispatchAttention into the done-after-waiting regression test so it
verifies both halves of the #5698 fix: the mid-turn permission pause
surfaces as attention exactly once, and the later genuine 'done' still
completes exactly once. Matches sibling tests that assert on both paths.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: brennanb2025 <brennankbenson@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(tabs): restore deferred tab activation so dragging a terminal tab doesn't switch panes mid-gesture
PR #5927 shipped terminal-pane drag (reorder tabs, move a tab into another
pane, edge-split into a new column). It deliberately DEFERRED tab activation
to pointer-up and suppressed it during a drag, so pressing a tab to drag it
never switched the active tab or stole terminal focus mid-gesture.
PR #6395 (d32d62a395) deleted tab-strip-pointer-activation.ts and made
SortableTab/EditorFileTab/BrowserTab activate eagerly on pointerdown — fixing
click-to-switch-after-reorder but regressing the drag: grabbing a tab now
flips the active tab + focused pane and yanks xterm keyboard focus before the
drag is even recognized (aggravated by PR #6210 raising the dnd-kit threshold
5px->12px). The move itself still lands, but the gesture feels broken.
Restore the deferred-activation hook, but gate it on measured pointer
DISPLACEMENT instead of the drag-active context ref the old hook used — that
ref clears asynchronously relative to the drop's pointerup, which is exactly
what made #6395's click-after-reorder misfire. Displacement mirrors dnd-kit's
own activation threshold: a release within TAB_DRAG_ACTIVATION_DISTANCE_PX is
a click (activate); crossing it is a drag (suppress). Because each press
measures its own gesture, a click after a reorder always activates.
- Recreate src/renderer/src/components/tab-bar/tab-strip-pointer-activation.ts
(displacement-gated) + unit tests, incl. an explicit #6395 regression case.
- Rewire onPointerDown in SortableTab, EditorFileTab, BrowserTab to use it.
- BrowserTab.test.tsx shallow-renders via mocked React hooks; add useRef/
useCallback stubs so the new hook works under that harness.
Verified end-to-end in a dev build via CDP: pressing a tab no longer
activates/steals focus on pointerdown; a click still activates on release;
dragging a tab into another pane still moves it; clicking tabs after a drag
still switches (the #6395 guard in tests/e2e/tabs.spec.ts stays green because
it uses zero-displacement clicks). 233 unit tests pass; oxlint + renderer
typecheck clean.
* fix: restore pane detach tab placement