* perf(ssh): reuse one proxy connection for system SSH on Windows
Windows OpenSSH cannot multiplex (ControlMaster fails with "getsockname
failed: Not a socket"), so every system-SSH exec for a ProxyCommand or
ProxyJump target re-ran the proxy handshake. Hold one `ssh -N -L` through
the real proxy per session and dial later execs over 127.0.0.1 with
HostName/Port/ProxyCommand=none/ProxyJump=none/HostKeyAlias overrides.
The route starts after connect, is proven with a login before use, falls
back to per-command proxying on any failure, honors
systemSshConnectionReuse: false, and is torn down with its proxy child.
Rebased from #31731 (commits b1cdd46, 3835c2d, 6268f2f) onto current main,
including the pending-route cancellation fix from b301b6a.
Co-authored-by: Neil <neil@stably.ai>
* refactor(ssh): stop the loopback forward through process-host's tree kill
Reuse signalProcessTree instead of a hand-rolled taskkill-then-kill
fallback: it already joins an in-flight tree kill, refuses a reaped PID,
and falls back to killing the root, so the route keeps no parallel copy
of that logic.
* fix(ssh): harden the Windows loopback route against review findings
- Manual targets: take the forward's sshd port and the HostKeyAlias from
the stored endpoint; `ssh -G` ran on the label and named another host.
- Hold the forward with ServerAlive probes so a silently dead proxy ends
it and commands fall back instead of stalling on 127.0.0.1.
- Never route forwards: their argv must stay stable for orphan recovery,
and the route's own forward must take the real proxy.
- Upload sessions read the route per file, so an exited route's port is
not dialled after it closes.
- Skip the route for security-key identities, whose forward and probe
logins would each ask for an unprompted touch.
* fix(ssh): keep the loopback route for manual targets no Host block claims
The unclaimed-alias endpoint rewrite only applies to config-backed targets,
so manual targets with a configHost no block names stay eligible.
* fix(ssh): keep the held loopback forward non-interactive
The forward is an optional background login; with BatchMode=no a password or
passphrase login raised an extra prompt right after connect, which the
non-interactive probe then discarded anyway.
* fix(ssh): admit the loopback route only when it changes nothing but the route
Overriding HostName to 127.0.0.1 changes which ssh_config blocks match, so a
`Match host <real host>` block could silently switch the remote User (or
identities, certificates, known-hosts files) for every routed command. Run
`ssh -G` with and without the route options and refuse the route on any
difference outside HostName/Port/HostKeyAlias/ProxyCommand/ProxyJump.
Also ask the agent with `ssh-add -L` and refuse the route when it may hold a
security key, so the background forward and probe never request a touch.
Both checks run through process-host and fail closed.
* feat(ssh): make Windows proxy connection reuse an opt-in per-target setting
The loopback route rewrites HostName, which ssh_config can key settings on,
so it no longer turns on by itself: a new optional systemSshProxyConnectionReuse
field (default off, Windows only) enables it from the SSH host's Advanced
settings. Old clients and servers ignore or omit the field.
Admission stays as the safety net and now:
- takes the forward's sshd port and HostKeyAlias from the direct `ssh -G`,
so a manual host's own Host block cannot send the route to another sshd;
- refuses any non-routing value with a `%` token, which `ssh -G` can leave
unexpanded while a routed login expands `%h` to 127.0.0.1;
- checks the security-key and agent identities of every ProxyJump hop;
- asserts the routed config really dials 127.0.0.1 with no proxy left.
* fix(ssh): offer proxy reuse only for hosts this desktop dials, refuse ${VAR} paths
- The switch now appears only for this desktop's own SSH hosts. A paired
server's add-host dialog never shows or sends it, so an older server's
strict schema is never handed the new field, and a client's platform no
longer decides a server's setting.
- Admission also refuses `${VAR}` references, which `ssh -G` prints
unexpanded while a login expands them.
- Advanced no longer auto-opens just for the opt-in.
---------
Co-authored-by: Bingxin Xu <xubingxin.xbx@gmail.com>
* fix(bitbucket): recognize the SSH host alias
* fix(bitbucket): recognize the SSH host alias on current main
Apply Chen's original SSH alias fix to current main, preserving canonical
Bitbucket browser links and adding case/port and lookalike-host coverage.
Co-authored-by: Chen <zwq19980411@gmail.com>
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(ai-vault): show Copilot sessions under their workspace with the original prompt as title
Copilot sessions in Agent Session History showed "Unknown location" and
titles prefixed with an injected <current_datetime> tag.
The Copilot parser only learned a session's folder from the
"Folder X has been added to trusted folders" notice, which Copilot does
not write for every session. It now reads the launch directory, git
root and branch from session.start's context, and the trust notice only
fills a folder the start did not name, so resume runs in the start
directory as it does for other agents.
User messages now take the original `content` before
`transformedContent`, which carries context Copilot injects.
`current_datetime` joins the hidden blocks so a transformed-only
message still gets a clean title.
Bump the session parse cache schema so histories cached with the old
parser are re-read once instead of keeping the wrong folder and title.
Fixes#30985
* fix(ai-vault): refresh cached Copilot metadata and search text
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(terminal): re-probe stale "missing" path results so links go live
The terminal link provider caches filesystem existence per pane, but the
cache had no expiry on negatives. A path printed a moment before its file
exists (e.g. an agent announcing a file it is about to write) got cached
as "missing" and stayed un-clickable forever, even after the file landed —
while a later mention of the same path resolved fine.
Give negative results a short TTL: a cached "missing" older than ~10s is
treated as a cache miss so the next hover re-probes the filesystem.
Positives stay cached (files rarely vanish mid-session, and a stale
positive fails gracefully on open). Cache entries become
{ exists, checkedAt }; both readers (the link provider and the click
fallback) go through the shared read helper so the TTL applies uniformly.
Refs #5024
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(terminal): preserve negative path cache expiry
* fix(terminal): ignore stale negative link probes
Overlapping hover scans can re-probe the same expired missing path. Stamp each probe with its start time and drop a result older than the cached one, so a late "missing" no longer hides a link a newer probe found.
* fix(terminal): order same-millisecond link probes with a sequence
* fix(terminal): retain expired link probe ordering
---------
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(automations): list a web client's paired server once, not again as Desktop
A paired web client's local runtime calls go to its server, so the desktop
authority and the saved runtime are one runtime. The Automations catalog
projected both and every automation appeared twice (Local <OS> + Orca Server).
The catalog now folds the desktop authority into the saved runtime that
getPairedWebClientEnvironmentId names, and a saved Desktop host filter falls
back to All hosts instead of waiting forever for an entry that never comes.
Co-authored-by: Chen <zwq19980411@gmail.com>
* fix(automations): move a saved Desktop host filter onto the paired server
On a paired web client the Desktop host is the server, so a saved Desktop
selection now names the server's matching host instead of being dropped with
a fallback notice on every visit.
Co-authored-by: Chen <zwq19980411@gmail.com>
* fix(automations): route web run links and sole-host choice through the folded server
Codex review: with Desktop folded into the paired server, a workspace run link
carrying hostId 'local' never matched a row and the run page stayed loading,
and the sole create host could be picked before the server's SSH targets
loaded. Pending navigation on a paired web client now names the server's
runtime, and desktop SSH hydration follows the runtime Desktop folded into.
Co-authored-by: Chen <zwq19980411@gmail.com>
* fix(automations): match web run links against the folded server at use time
Resolving the pending navigation's host when it is matched, not when it is
stored, also covers a link opened before the server environment list loads.
Replaces the store-setter rewrite from the previous commit.
Co-authored-by: Chen <zwq19980411@gmail.com>
---------
Co-authored-by: Chen <zwq19980411@gmail.com>
* feat(cli): add orca ports to list a workspace's detected ports
Scripts and agents had no way to read the ports Orca already detects for
a workspace, so finding a dev server on a random port meant redoing the
lsof scan by hand. `orca ports` reuses the host-scoped
workspacePorts.scanHost RPC, keeps the rows owned by the current (or
--worktree) workspace, and prints port, pid, process, connect host,
protocol, how the owner was matched, and the advertised URL. --all shows
every listener on that host. An older runtime without scanHost gets an
"update Orca" error instead of a raw method_not_found.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): resolve folder workspaces in orca ports and match rows by path
Run inside a native folder, `orca ports` failed with selector_not_found
(or picked an enclosing git worktree) because it only searched the
worktree list. It now resolves the cwd like `agent launch`, native
folders first, and shows that host's listeners. Rows are matched on the
owner's path instead of its id, so every instance of a repo-backed
folder workspace keeps its ports even though attribution names only the
first instance as owner. `--all` adds the worktree id when two owners
share a display name, and the skill guide notes that only workspace
rows carry `owner` and `advertisedUrl`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): refuse orca ports for folder workspaces without --all; document the command
Co-authored-by: Manuel Meurer <manuel@meurer.io>
* fix(cli): recognise native folder path selectors in orca ports; note SSH scan limits
Co-authored-by: Manuel Meurer <manuel@meurer.io>
* fix(cli): refuse a folder path shared by several folder workspaces in orca ports
Co-authored-by: Manuel Meurer <manuel@meurer.io>
* fix(cli): refuse a path a worktree and a folder workspace share in orca ports
Co-authored-by: Manuel Meurer <manuel@meurer.io>
---------
Co-authored-by: Manuel Meurer <manuel@meurer.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
With automatic symlink following disabled, re-reading a parent lists its links as files and discards the directory flag set when a user opened one. Keep that flag while a successful same-owner child listing is cached, in both direct reads and expanded-directory refreshes.
Mark inferred folder rows separately so a failed child read can clear them without changing host-confirmed directories. Preserve normal deleted-link and missing-target recovery behavior. Add parent/full refresh, failed read, ownership and Windows drive-root regression coverage.
Refs #29069.
Co-authored-by: Arnobrizwan <147672066+Arnobrizwan@users.noreply.github.com>
* fix(editor): resume media previews from their last playback position
Switching tabs unmounts the editor surface, so the media element was
recreated and every video or audio preview restarted from zero. Record
the playback time on teardown and seek back once metadata loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(editor): resume media previews from their last playback position
Switching tabs unmounts the editor surface, so the media element was
recreated and every video or audio preview restarted from zero. Record
the playback time on teardown and seek back once metadata loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(editor): verify media remount playback and cleanup
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(runtime): retire predecessor exit evidence on incarnation change
When a PTY id is re-registered with a different, known incarnation, the handle was already invalidated but lastExitCode/lastExitCause (pty and leaves) still carried the predecessor exit, so a live replacement reported the old stop. Clear them at that same proven-replacement point. Same or unknown incarnation keeps the evidence (fail closed).
* fix(runtime): retire exit evidence for replacement incarnations
Clear predecessor exit evidence on the PTY and its indexed leaves when
registration or provider spawn proves a different known incarnation owns it.
Cover both paths so an earlier spawn cannot hide the transition from later
registration. Keep same and uncertain incarnation evidence conservative.
Adapted from Cubatica's original fix and regression cases in #24513; extend
the coverage to spawn-first replacement on the current runtime modules.
Co-authored-by: Cubatica <4414418+Cubatica@users.noreply.github.com>
---------
Co-authored-by: Cubatica <4414418+Cubatica@users.noreply.github.com>
* fix(terminal): focus the neighboring pane when a split pane closes
* test(terminal): cover focus after closing a split pane in the app
* fix(terminal): focus the neighboring pane when a split pane closes
* test(terminal): cover focus after closing a split pane in the app
* test(terminal): keep pane-close parity exclusions evidence based
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(terminal): shape Bengali and other Indic clusters in the WebGL renderer
xterm gives each Brahmic spacing vowel sign its own cell (matching
wcwidth/string-width, which TUIs align on), and the WebGL renderer draws
every cell in isolation. Pre-base vowels and conjuncts were never
composed, so Bengali rendered as detached marks on dotted circles.
Join each multi-cell Indic cluster through the existing lazy character
joiner so the browser shapes it as one run. Cell widths are unchanged,
so TUI layout and borders keep aligning.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(terminal): drop redundant Indic predicate test, make fresh-array test meaningful
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(terminal): stop Indic joiner over-joining Tamil/chillu and registering for non-joining text
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(terminal): keep a trailing Indic cluster prefix between lazy-joiner writes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): let Cmd/Ctrl +/- zoom an open image
The zoom shortcut on an image tab changed the hidden editor font zoom,
because only PdfViewer took the app zoom request. ImageViewer in the
command-owning pane now takes it too: it zooms the popup while that is
open and the inline image otherwise. Intrinsic-layout images in a
combined diff keep editor font zoom. The request module gets a
viewer-neutral name now that it serves PDFs and images.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(editor): zoom a combined-diff image's popup from the shortcut
Intrinsic-layout images skipped the app zoom request even while their
popup was open, so Cmd/Ctrl +/- left the popup unchanged in a combined
diff. Skip them only while the popup is closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(editor): let Cmd/Ctrl +/- zoom an open image
The zoom shortcut on an image tab changed the hidden editor font zoom,
because only PdfViewer took the app zoom request. ImageViewer in the
command-owning pane now takes it too: it zooms the popup while that is
open and the inline image otherwise. Intrinsic-layout images in a
combined diff keep editor font zoom. The request module gets a
viewer-neutral name now that it serves PDFs and images.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(editor): zoom a combined-diff image's popup from the shortcut
Intrinsic-layout images skipped the app zoom request even while their
popup was open, so Cmd/Ctrl +/- left the popup unchanged in a combined
diff. Skip them only while the popup is closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(editor): give image popups exclusive app zoom
* fix(editor): scope viewer zoom to the focused workspace surface
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(mobile): start dictation from agent chat tabs
The mobile mic only started dictation when a terminal was active. An
agent chat tab has no terminal handle, so a tap there returned without
starting, opening setup, or showing an error.
A chat transcript goes to the chat composer, not a terminal, so chat
tabs now only need a connected desktop. Terminal tabs keep the
terminal input gate.
Fixes#26003
* test(mobile): verify dictation delivery and reconnect safety
* test(mobile): satisfy dictation React Doctor checks
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(runtime): collapse mirrored editor tab variants that differ only by environment
A snapshot that already lists one file under several per-environment host tab ids
(editor:<worktree>:<env>:<path> or the bare path) fanned out into one mirrored tab
per variant. Keep one minted variant per (worktree, mode, path) when building
mirrored editor tabs; tabs with opaque ids (deliberate splits) are untouched.
Protects receivers from older peers that still republish mirrored files and from
snapshots that are already polluted.
* fix(runtime): match encoded editor tab ids and keep the navigation target when collapsing variants
Host tab ids come from buildOwnedEditorFileId, which URL-encodes the worktree,
environment and path, so the raw-path predicate never matched a real variant.
Each candidate is now checked by rebuilding it with buildOwnedEditorFileId, and
the tab a pending focus/follow intent targets wins its bucket ahead of an active
one. Tests build their ids the same way.
* fix(runtime): preserve editor identity while collapsing aliases
* test(runtime): keep headless mirror regression across project boundaries
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(mobile): preserve agent icons across stacked routes and tab updates
Closes#31939
* fix(renderer): address review feedback on mobile agent icon stability
* fix(renderer): fall back to customTitle when saved leafTitle is blank
* Keep mobile agent icons painted across retained routes
Use instance-specific OMP gradients and preserve bundled icon fallback during web image reload failures. Add a real mobile-web route-retention pixel regression.
Adapted from zhangxaochen’s scoped rendering fixes in #31948; unproven session equality and title changes are excluded.
Co-authored-by: zhangxaochen <zhangxaochen@163.com>
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(markdown-preview): keep the #L line when a link opens a file in another worktree
The Preview scheduled the line reveal on frames it cancels when it unmounts, and opening a file in another worktree unmounts it. Schedule the reveal through the store-level helper Rich mode already uses.
* test(markdown-preview): assert the cross-worktree reveal carries the opened file's id
The editor applies a pending reveal only when its fileId matches the open
record, so register an owner-qualified record from openFile and check the
reveal targets it rather than the path fallback.
* fix(markdown-preview): preserve line reveals across preview teardown
Adopt Samuel Vila’s app-owned editor reveal scheduling and strengthen teardown and superseding-navigation coverage.
Co-authored-by: Samuel Vila <svila.smr@gmail.com>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(ai-vault): prevent virtualized session rows from collapsing to 0px and overlapping
* fix: preserve measured vault row sizes while hidden
* test: cover vault estimates receiving real measurements
* fix(history): retain session row measurements while hidden
Use the existing hidden-row measurer so native zero-size notifications cannot corrupt cached session geometry or move the restored list. Adapt the original regression to cover measured expanded and collapsed offsets across hidden notifications and subsequent visible sizes.
Co-authored-by: Xulin Wu <wuxulin@bytedance.com>
---------
Co-authored-by: Xulin Wu <wuxulin@bytedance.com>
Co-authored-by: Neil <neil@stably.ai>
* fix: clear GitHub comment composer after submission
* docs: document comment composer and submission harness
* fix(github): keep submitted comments cleared
Suppress Tiptap content updates caused only by editability changes, and
exercise actual comment submission, failures, retries and external values.
Co-authored-by: s34485 <an_sunwoo@cyberagent.co.jp>
* Type the comment composer test store selector
Derive the named test owner settings from AppState while retaining the same runtime fixture. Fix the anti-slop no-object-parameters CI failure.
---------
Co-authored-by: Neil <neil@stably.ai>
pnpm dev launches the app as com.stablyai.orca.dev, but the macOS helper's
isTrustedOrcaApplication only trusted com.stablyai.orca, com.github.Electron
and ids starting with com.stablyai.orca.dev. (with the trailing dot). Every
computer-use request from a dev build failed with "computer-use agent peer is
not authorized".
Move the bundle id rule into TrustedOrcaApplication in
OrcaComputerUseMacOSCore so it is tested, and trust com.stablyai.orca.dev
exactly as well. Look-alikes such as com.stablyai.orca.devtools stay
untrusted.
Fixes#26498
Co-authored-by: petersindex <287557875+petersindex@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
Linear returns an issue's comments newest first and getIssueComments
passed that order through, so the activity feed showed the newest comment
on top while comments posted from Orca were appended at the bottom and
jumped to the top on reload. Sort by createdAt, oldest first, like the
GitLab notes path does.
Fixes#25573
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(ui): align multiline filter toggle row text and icons to top-left
- Add `text-left` and `items-start` to FilterToggleRow button and label wrapper.
- Add `mt-0.5` top margin to icon and switch indicator for optical alignment with multiline text.
- Resolves center-aligned wrapped text and vertically misaligned toggles under long localized labels (Korean, Japanese, Spanish, etc.).
* fix(ui): align multiline filter toggle labels with their controls
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(sidebar): keep Cmd+Shift+Arrow cycling in place after closing a workspace
Closing a workspace's last tab clears activeWorktreeId (landing fallback), so
cycling had no anchor and re-entered the sidebar from its far end — every
subsequent Cmd+Shift+Down/Up restarted there instead of continuing from where
the user was.
Resolve the anchor from the nav-history cursor when nothing is selected. A
selection that is present but uncyclable still passes through untouched, so a
row inside a collapsed group keeps entering from the matching end (#10513).
History is read via getState() at press time; subscribing would re-render the
virtualized sidebar on every visit.
* fix(sidebar): include folder workspaces in Cmd+Shift+Arrow cycling
getCyclableWorktreeIds dropped every non-item row, so a folder workspace sitting
in the sidebar could not be reached with the arrow chord even though Cmd/Ctrl+1-9
already numbers it. Cycling also activated through activateAndRevealWorktree,
which has no route for a `folder:` key.
Build the rotation with getRenderedWorktreesInSidebarOrder — the helper Cmd+1-9
already uses, so both shortcuts cover the same rows in the same visual order —
and activate through activateAndRevealWorkspace, which dispatches on the key.
Rows still come from what the sidebar rendered, so collapsed groups stay out.
* fix(sidebar): keep the cycle anchor host-qualified after a close clears the selection
Closing a workspace's last tab nulls both the selection and its host, and
nav history stores no host, so the history anchor cycling falls back to
resolved to the first rendered row with that id. With the same worktree
id on two hosts, closing the twin on the second host made the shortcut
cycle from the first host's row.
The keyboard hook now remembers the last selected (workspace, host) pair
and hands that host to the anchor when the anchor is that workspace. Any
other history anchor stays host-less as before. Nav history is persisted,
so its entry format is left alone.
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(gitea): treat HTTP 404 as no-PR in throwing hosted-review lookup
hostedReview:forBranch always uses the throwing Gitea lookup variant, and
requestJsonAtBase threw on any non-OK status. A 404 (PR deleted, repo absent
on the forge, or Gitea masking a private repo) rejected the IPC handler and
spammed the main-process log on every refresh for every unaffiliated branch.
404 means the resource is absent — an accepted "no PR", not a transport or
auth failure. Map it to null even with throwOnFailure; 401/403/5xx/network
errors still throw so creation eligibility keeps reporting 'unavailable'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(gitea): scope the 404 no-PR exemption to the linked-PR fetch
A blanket status !== 404 exemption in the shared request helper also
covered the repo-level /pulls scan, where a 404 means a wrong API base
URL, a repo absent from the forge, or a token without scope. Collapsing
that to "no PR" would make a misconfigured Gitea host look permanently
PR-less instead of surfacing reviewLookupOutcome: 'unavailable'.
Gate the exemption on an explicit allowNotFound request option, set only
on the single-resource /pulls/{n} fetch.
Co-authored-by: Orca <help@stably.ai>
* test(gitea): pin the 200-with-empty-list no-PR shape alongside the 404 case
Co-authored-by: Orca <help@stably.ai>
* fix(gitea): preserve deleted linked pull request absence handling
Refresh the original Donach PR and its prior maintainer correction on current main, keeping the HTTP404 exemption limited to the linked pull resource.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(editor): render <br> in markdown table cells as line breaks in rich mode
The rich Markdown editor encoded every inline HTML tag — including `<br>` and
`<br/>` — into an inert raw-HTML atom that displayed the literal tag text. In a
table cell, where a blank-line break is impossible, this meant `<br>` showed up
as raw "<br>" instead of breaking the line.
Render `<br>`/`<br/>` raw inline nodes as real break elements while keeping
`renderMarkdown` emitting the verbatim tag, so the value round-trips exactly on
save. A native hardBreak is intentionally not used: it serializes to a space
inside table cells and would silently drop the tag. The raw-source node's
parse/render DOM logic is extracted into a dedicated module to keep it cohesive
and within the file's line budget.
Closes#8838
* fix(editor): validate preserved break source
Use the editor model's supported DOM parse-rule type instead of a private duplicate. Exercise the rendered-DOM round trip and reject forged preservation attributes before they can become hidden Markdown source.
* test(editor): assert forged break marker exists
* test(editor): preserve authored rich markdown break spelling
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
The workspace space-manager rendered every hosted review as "PR #N", so a
GitLab merge request showed as "PR #5" instead of "MR !5". The cached review
already carries its provider (HostedReviewInfo.provider) — the panel's local
input type just narrowed it away. Thread provider through and format via the
same localizedHostedReviewCopy / '!' vs '#' helpers the rest of the app uses.
Extract the label formatting into hosted-review-label.ts (a pure module, away
from the panel's heavy React/store imports) so it can be unit-tested directly,
and add hosted-review-label.test.ts covering github / gitlab / azure-devops /
gitea and the status suffix.
linkedPR stays "PR #N" — it is the GitHub-specific workspace field, distinct
from linkedGitLabMR.
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: Neil <neil@stably.ai>
* fix(review): show Remove Project label in PR panel for main worktree
When a merged PR is linked to the main worktree (the project root /
default branch), the PR sidepanel delete button now reads "Remove
Project" instead of "Delete Workspace", matching the project-removal
dialog that runWorktreeDelete already opens for the main worktree.
Adds the b1a767b1ae i18n key (Remove Project) across en/ko/ja/zh.
* chore(i18n): apply AI code review suggestions
Rename the newly added HostedReviewActions key from the codemod-style
content hash b1a767b1ae to the intent-named id removeProject, per the
message ID policy in config/i18n-translation-source.md: new keys must
use intent-named IDs, and hash IDs minted after 2026-07-30 are not
grandfathered.
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
findActivePathCommand now .catch(()=>null)s inspectSymlink so an execute-only foreign PATH `orca` is skipped instead of rethrowing EACCES; blocks the readFile EACCES at cli-installer.ts:551.
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Neil <neil@stably.ai>
Added [@media(hover:none)]:ml-0/max-w-5/opacity-100 touch reveal to REPO_HEADER_ACTION_REVEAL_CLASS so repo/group header action buttons show on touch devices.
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Neil <neil@stably.ai>
* fix(ui): use focusable tooltip menu triggers
* test(tooltips): give each menu surface its own remount check
Keep all hover, focus, description, keyboard, pointer and two-remount
assertions while avoiding a shared 120-second budget across three surfaces.
The failed CI trace passed five full exercises before global teardown.
---------
Co-authored-by: Yuris Auzins <zuz666@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(composer): keep the imported group as the target after a grouped nested import
Grouped folder imports create a ProjectGroup, but the hosted Add Project
handoff selected only the first imported repo, dropping the group the user
explicitly chose. Carry the root group id through the completion path and
select the group's folder-workspace target in the composer, falling back to
the single-project handoff when no group was created or it is no longer
selectable. Sidebar default-checkout and separate imports are unchanged.
Part A of #9529.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidebar): document the CompleteGitRepoAdd handoff contract
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: just-zzp <264744947+just-zzp@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(orchestration): refuse send to a Run whose home is another computer
A remote Dispatch's Run id also names a mirror row on the worker's host. Once the
worker lost its link to the home, send --to run:<id> wrote into that mirror and the
message reached no one. Now the host refuses with run_home_unreachable, and a linked
worker that names its own Run home is delivered there.
Reported by @cookapps-jhlee8 in #32292.
* fix(orchestration): refuse only mirror Runs, and answer coordinator mail at its Run home
A loopback runtime's attachment names a real local Run, so the refusal now keys on the
Run row's remote home, not on any attachment. A reply to coordinator mail imported under
a remote Run relays to the home through the replying pane's Dispatch link, and is refused
with run_home_unreachable when that link is gone.
* fix(orchestration): a remote reply answers only mail sent to the replier's own Dispatch