* perf(i18n): load built-in catalogs once, without copies or a runtime plural fill
Each catalog load parsed the JSON, rebuilt it to fill CLDR plural forms
from _other, then deep-copied and deep-merged it into the store (about
26 ms after parsing per 1.3 MB catalog), and concurrent loads of one
locale fetched it twice.
- sync:localization-catalog now commits the plural forms a locale's
CLDR rules need beside each _other (fr/es/pt many, tr one, a few
de/ru), and verify:localization-catalog fails when one is missing.
Plural forms may include {{count}} where English omits it.
- Renderer and main load built-in catalogs as-is; plugin packs keep the
runtime fill.
- Renderer catalog requests are shared per locale; a timed-out request
is dropped so a retry fetches again. Bundles are added with skipCopy.
* fix(i18n): require {{count}} in plural forms when English shows it
* refactor(i18n): share one i18next setup between main and renderer
- createOrcaI18n (src/shared/orca-i18n-instance.ts) owns the backend,
init options, plugin-pack registration, resource-language resolution
and the translate wrapper; main and renderer inject only how a
built-in catalog loads (dynamic import vs. fetch) and extra modules.
- Remove setRendererUiLanguage (tests only); tests load then switch.
- resolveUiLocale maps built-ins by lookup instead of an if-chain, and
catalog URLs/loaders are direct map lookups.
- Drop <I18nextProvider>: initReactI18next already binds the instance.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): release skill scans from retired transport generations
* fix(mobile): read skill-scan transport generation when the request starts
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): refuse PDF opens from hidden pages
* test(mobile): assert one refused hidden PDF open instead of scaled loops
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): preserve long terminal and chat pastes
Agents can keep only the last ~1 KiB read of unframed input (#13332).
Frame long single-line chat messages like multi-line ones, and frame Live
input appends over 1 KiB (an OS paste) with the terminal's paste rule,
including held text flushed before a terminal click.
Co-authored-by: Carlos Baraza Haro <carlos.baraza@ieee.org>
* fix(mobile): end the live input session after a framed paste
Agents such as Codex collapse a framed paste into one token, so later
per-character erases from the field mirror desynced the line. A long
append to a terminal in bracketed paste mode now commits the whole field
in one frame and clears the field, recording it like other session ends
so a late native echo cannot send the paste twice; without that mode the
paste stays unframed and editable. Chat sends measure the sanitized bytes
when deciding to frame.
---------
Co-authored-by: Carlos Baraza Haro <carlos.baraza@ieee.org>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* fix: retire editable context menus with renderer documents
* fix: retire edit menus opened during a pending reload at commit
A right-click while a cross-document reload is pending opens a menu after
the navigation fence fired. Close it at main-frame commit and drop clicks
from retired menus, since macOS closePopup is posted asynchronously.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* perf(i18n): load built-in catalogs once, without copies or a runtime plural fill
Each catalog load parsed the JSON, rebuilt it to fill CLDR plural forms
from _other, then deep-copied and deep-merged it into the store (about
26 ms after parsing per 1.3 MB catalog), and concurrent loads of one
locale fetched it twice.
- sync:localization-catalog now commits the plural forms a locale's
CLDR rules need beside each _other (fr/es/pt many, tr one, a few
de/ru), and verify:localization-catalog fails when one is missing.
Plural forms may include {{count}} where English omits it.
- Renderer and main load built-in catalogs as-is; plugin packs keep the
runtime fill.
- Renderer catalog requests are shared per locale; a timed-out request
is dropped so a retry fetches again. Bundles are added with skipCopy.
* fix(i18n): require {{count}} in plural forms when English shows it
* chore(i18n): sync plural forms for catalogs merged from main
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): give agent questions one answering surface that fits the screen
A shown prompt card now replaces the chat composer, as on desktop. The
composer stays mounted underneath so its draft and attachments survive,
and comes back when the card collapses, clears, or dictation is running.
The card keeps the conversation visible above it (60% cap) and takes the
whole area while its reply is being typed. Question and Ask cards scroll
inside that bound and keep a focused reply in view as the card shrinks.
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: fancivez <fancivez@gmail.com>
* fix(mobile): open each ask step at its question
The ask card's body now scrolls inside a bounded card, so a new step must not inherit the previous step's scroll offset.
* fix(mobile): keep the queued tray shrinkable and approval actions reachable
The composer wrapper passes the remaining height down again, the queued-message tray unmounts while a card owns input so a focused editor cannot keep typing out of sight, and an approval card scrolls as a whole when its choices exceed the card's bound.
* fix(mobile): keep the composer for an approval this build cannot answer
Its choices are disabled and only a new turn can cancel it, as desktop's pendingPromptsAllUnanswerableHere rule keeps the composer for.
* fix(mobile): keep the approval card's choices pinned and the composer for a terminal-only ask
A long plan shrinks inside the approval card again instead of pushing its choices below a scroll. An ask only the terminal can answer leaves the composer (and Stop) in place, like an approval this build cannot draw.
* fix(mobile): keep the queued tray mounted under a prompt card
Unmounting it dropped its in-flight guards, so Resume could run while Clear all was still deleting. A hidden focused editor loses focus with display none.
* fix(mobile): keep the composer while dictation lands, and each ask step at its question
Transcribed text arriving after the mic stops stays visible in the composer. A focused reply is revealed only when its card shrinks, so a step that grows the card keeps its reset to the question.
* fix(mobile): keep queued work in view under a question, and end an ask step's edit on navigation
Desktop leaves queued messages and background-task controls visible while a prompt card shows; only the composer steps aside. Moving between ask steps ends the reply edit so typing can't land in a field scrolled out of view.
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: fancivez <fancivez@gmail.com>
* fix(wsl-host): a fresh host's first agent launch waits for the desktop's account
On a WSL host with no saved account env, an agent launched in the first
second could start on the distro's own login before the desktop's first
push landed. Agent terminals, structured chats and AI commit text now wait
up to 5 s, once, for that push.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(wsl-host): the startup restore arms the first-push wait only on a WSL host
Co-Authored-By: Claude <noreply@anthropic.com>
* test(wsl-host): the desktop and non-WSL hosts never wait or start a timer before a launch
* fix(wsl-host): refresh the selected Claude folder before the host's own Claude launches (#29589)
* fix(wsl-host): refresh the selected Claude folder before the host's own Claude launches
A WSL host's orcad launched Claude on the account folder as the desktop set
it up when the account was added, so Claude replayed its first-run screens
and missed later settings. orcad now refreshes that folder from the distro
user's ~/.claude (state minus login keys, settings) before each Claude launch
it starts, with the desktop router's 5 s cap, reusing the guest helper's
provisioning.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): refresh a newly selected account after a mid-refresh switch; skip non-Orca folders
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(wsl-host): move the switch-aware Claude folder refresh out of the account controller
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(runtime): move the host account launch env into its own module
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(linux): stop the serve `orca` dispatcher shadowing the screen reader, and remove it with the CLI
Headless `orca serve` writes ~/.local/bin/orca so Claude Team's bare `orca`
reaches Orca even when a login shell drops Orca's PATH entry. It exec'd the
launcher unconditionally, so it sat in front of GNOME's `orca` screen reader for
everyone, failed with "No such file or directory" once Orca was uninstalled, and
nothing ever removed it.
The dispatcher now answers for Orca's own children only (ORCA_CLI_BIN_DIR,
ORCA_PANE_KEY or ORCA_USER_DATA_PATH set). Anyone else gets the next `orca` on
PATH, skipping itself, with one hop so two dispatchers cannot loop; then this
app; then a clear message and exit 127 once the launcher is gone. Existing
dispatchers are rewritten on the next serve start. Removing the `orca` command
now also removes the dispatcher, and only if it carries Orca's marker.
* fix(linux): skip every Orca dispatcher on PATH instead of an env hop guard
The hop variable let a second dispatcher earlier on PATH send a screen-reader
user's `orca` to Orca, and leaked into the screen reader's environment so its
descendants' `orca` could land on Orca too. The PATH scan now skips any
candidate carrying the dispatcher marker (as well as itself), so dispatchers
cannot hand calls to each other, and the real target runs with nothing added to
its environment.
* test(linux): Orca's own children reach the screen reader once Orca is gone
* fix(linux): Orca's own children get "no longer installed", never the screen reader
With ORCA_CLI_BIN_DIR, ORCA_PANE_KEY or ORCA_USER_DATA_PATH set and the
launcher gone, the dispatcher now prints the message and exits 127 without
scanning PATH, so an agent's `orca …` can never start GNOME's talking screen
reader. The PATH scan also ignores relative entries, which depend on the
caller's folder. Tests cover each Orca variable, a PATH folder with spaces,
empty and relative entries, a symlink to the dispatcher, and an unreadable
candidate being run rather than skipped.
* fix(mobile): show live input caret and preserve selection edits
Display the existing input directly so its native caret shows. Accessory
Backspace/Delete edit at the field's caret, read from the input when pressed,
so edits that keep the caret (IME composition) still delete, and the caret is
restored for subsequent typing.
Port and adapt community PR #28294 to the current command dock.
Co-authored-by: Kyino <kyino1025@gmail.com>
* fix(mobile): keep live input caret keys local and the dock height fixed
Hardware Left/Right/Home/End moved both the visible field caret and the
PTY cursor, so the mirror erased at the wrong place. With field text they
now move only the field caret, like Backspace and Delete; Right/End at the
field end still reach the PTY so shells can accept an autosuggestion. On
Android the key binding lets plain caret keys through to that decision
instead of sending them to the PTY. The dictation and upload status covers
the field instead of adding a row, so starting or stopping either no longer
resizes the terminal.
* test(mobile): annotate the DOM field cast in the accessory input test
* chore(mobile): allowlist the live input field strings for localization coverage
---------
Co-authored-by: Kyino <kyino1025@gmail.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* fix(mobile): preserve browser typing across delayed keyboard sends
A failed send restores an old draft over newer typing, null success is treated as refusal, and rapid presses duplicate sends. Preserve newer typing, interpret acknowledgement explicitly, and fence page/client ownership.
* test(mobile): preserve browser drafts through same-computer connection cutover
* test(mobile): record browser keyboard acknowledgement semantics
* fix(mobile): warn only when a browser keyboard send's delivery is unknown
A refused send restores the text silently as before; only a lost reply or connection cutover shows the unconfirmed toast, using the app's existing unconfirmed-delivery wording.
* fix(mobile): keep a browser keyboard send's text through a reconnect
The pane remounts per page, so a reconnect that swaps the client keeps the same draft; only unmount should stop a late reply from restoring it.
* fix(mobile): warn only for written browser keyboard sends with lost replies
A cutover error already carries the delivery-unknown mark when its request was written, so the extra cutover check only warned about text that never left the phone. Classifying outside the resolved path also keeps an unconfirmed send from clearing the error banner.
* fix(mobile): let newer browser typing send while an earlier send is in flight
Claiming the draft from a ref stops a repeat tap before re-render without blocking a second send of newly typed text.
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* feat(wsl): convert UNC Windows-drive projects to drive paths across a restart (D1c-4c)
* fix(wsl): read the conversion's field table from its shared home
* test(wsl): the conversion runs Windows Git only on the drive path, since WSL denies UNC drive folders
WSL 2.7.11 answers EPERM for \\wsl.localhost\<distro>\mnt\c\... from Windows. Pin that the
preflight's Git runs and worktree-link reads use C:\... by making every UNC access throw EPERM.
* fix(wsl): name the cause when a conversion refuses over saved state
A refusal over saved state now says whether a project also holds an unconverted repo, or a drive
key already holds different data (and which key), instead of a bare collision code. The fields are
optional, so the window's copy still reads an older host's refusal. The seeded shape (a saved UNC
worktree with tabs beside the bare drive row startup discovery wrote) now converts.
* test(wsl): startup rolls back only when the drive key holds different user data
* fix(wsl): connect the conversion's pause to a WSL host as a user action, as #34059's mover now requires
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit bfeb4fe3ae7f278ac452e376d426315dbf7b985c)
* fix(wsl): never report a committed conversion as rolled back; record and check its profile; refuse paths off a Windows drive mount
- The journal's post-commit phase write sat inside the rollback catch, so a failure there
dropped the journal and reported rolled-back for a profile already rewritten. The write now
follows the commit outside the catch: a failure leaves the journal 'prepared', whose rewrite
is idempotent, and reports resuming.
- The journal records the profile it rewrites, and startup runs only the active profile's.
- The run reads the distro's Windows-drive mounts and passes them to the scope.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(wsl): type the journal-failure switch without a cast
* fix(wsl): only this profile's waiting conversion blocks starting another
* test(wsl): compare the two profiles' journals without depending on directory order
* fix(wsl): a torn shell-history meta.json never stalls a conversion, it is written atomically, and only a prepared conversion blocks a new one
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
* fix(wsl): publish the resolved WSL hosts gate state to child processes
Children inherit ORCA_INTERNAL_WSL_HOSTS=1 even when a remote policy block
closed the gate, and none inherit serve-only. Main now publishes its
resolved state as ORCA_WSL_HOSTS_GATE_STATE at startup, and the shared
serving check in a child trusts it over the internal gate env. A process
that resolves the gate itself ignores an inherited value, so a relaunch
never carries a stale state. ORCA_INTERNAL_WSL_HOSTS is left intact so a
relaunched or updated Orca keeps fetching the policy.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl): main ignores an inherited WSL hosts gate state from its first line
A relaunch or the updater's installer inherits the parent's published
ORCA_WSL_HOSTS_GATE_STATE. Main's entry now marks the gate as its own to
resolve before preflight, so even a read before configure falls back to
the internal gate env, and configure always overwrites the value. Tests
pin that a stale inherited closed or open is ignored and overwritten.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* test(wsl): wait for the chat provider's report to settle instead of reading it once
* test(wsl): wait for the resumed turn's message, not just its launch
A WSL connect read the activation record, the transaction journal, the
activation fence and orcad's instance lock in separate guest commands,
some of them twice, before taking the fence. Read them once, alongside
the record the context already read, and hand that snapshot to recovery,
adoption and the deploy's pre-upload checks as a hint. A hint only skips
a read whose answer would let the step continue unchanged; anything else,
and every fenced decision, still reads the host. SSH passes nothing.
* perf(i18n): skip unchanged plugin language packs on plugin-list refreshes
Every plugin-list refresh produced a new entries array (even an empty
one), so I18nProvider removed and re-added every pack as a full copy and
ran changeLanguage, whose languageChanged re-rendered the whole tree.
- The plugin content list store reuses structurally unchanged entries
(and the previous array when nothing changed).
- Pack registration re-adds only packs whose catalog changed, in main and
renderer; main rebuilds menus only when something changed.
- I18nProvider re-applies the language only when the selected pack
changes, which keeps forced re-application for real pack updates.
* fix(i18n): match reused plugin entries by id; keep the namespace after pack removal
removeResourceBundle drops the translation namespace that later backend
loads read, so removing one pack while others stay unchanged broke lazy
loading of built-in catalogs.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(wsl): start a bridge fish older than 2.7 without -C instead of failing the pane
fish before 2.7 rejects -C, so the claude function and cwd hook made a
WSL fish pane fail to start on distros such as Ubuntu 16.04. Read the
version first and start those plainly. The cwd-report test now decodes
reports through the daemon's parser and checks fish 4's own reports.
* fix(wsl): read a fish 2.x version from stderr
fish 2.x prints --version to stderr, so the probe read an empty string
and old fish started with -C and failed. Read both streams; the stub now
reports one old version on stderr so the test pins this.
* fix: allow dismissing workspace auto-name failures
* fix: retain auto-name failures until dismissal is saved
* refactor: use workspace selectors for metadata state updates
* test(e2e): background work never starts a stopped WSL host distro (E9 guard)
A desktop on the fake WSL transport keeps a WSL-host project and a local one. With the distro
stopped, a window of background activity elsewhere (window focus/blur/reveal, workspace switches,
linked issue/PR cards, a streaming chat in the local workspace, Settings panes and Tasks, account
switches) must start nothing; opening a terminal in the WSL workspace then starts it exactly once.
createGitProject returns the real path so the helper also works where tmpdir is a symlink.
* test(e2e): actually render each Settings pane in the E9 background window
openSettingsTarget alone only set the navigation target, so no pane mounted and none of its reads
ran. Open Settings, wait for each pane's section to mount, then close it. A repo pane that boots
the distro on mount now fails the guard.
* test(e2e): fail the host-mode E9 guard early on a missing repo or connect log; state its scope
nushell (0.94+, shell_integration.osc7 on by default) percent-encodes
only non-ASCII bytes in its OSC 7 report. The daemon's URL parser cut the
path at a raw `#` or `?` (recording the wrong folder) and rejected a `%`
not followed by two hex digits (dropping the report). The parser now
encodes those three in the path first. A properly encoded URI never has
them there, so every existing sender parses byte-identically, pinned by a
104-case snapshot taken before the change.
One pinned result changes on purpose: a folder named `%ZZ` now parses as
that path instead of being rejected. Non-UTF-8 sequences such as %FF are
still rejected, and control-byte handling is unchanged.
Also pins that the WSL bridge starts nushell as a plain login shell with
its own config, so its own report survives.
When upsertDelegatedMobileDevices returns fewer children than phones
(a child removed on disk meanwhile was dropped by the save), rerun it
once against the current parent so the phone gets a working child now
rather than at the next sync.
* feat(wsl): let a cached remote policy close the WSL hosts gate (kill switch 2/2)
A cached remote policy of "block" now closes an internal gate the launch
environment opened, for the whole run. It reuses the closed-gate path that
already exists: with saved hosts or moves the gate becomes serve-only, so
moved projects keep working while nothing new is added or moved. With
nothing saved it is closed. A missing, unreadable or "allow" policy changes
nothing. Main stamps windows so preload agrees, the download runs only on
Windows with the internal gate open, and telemetry reports the block.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(wsl): report the applied WSL hosts policy and keep the way back under a block
Telemetry reports the cached policy applied at launch as allow, block or
missing (omitted when the internal gate left it unread). Tests pin that a
block keeps Remove host, the way back and unfinished-move recovery, refuses
new hosts and moves, and that a mid-run download applies only next launch.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Split first output into ensure, terminal create, and send-to-output, and
report orcad's internal startup medians (boot before the first timed phase,
each phase's end and duration, readiness) so moving the readiness publish
earlier can be judged per phase.
Adds OrcadFeatureReadiness (accounts, sessionSearch, orchestration,
unshownPlacement) and a requires: field on defineMethod. The dispatcher
waits a bounded 10 s for a method's features, then refuses with the
existing retryable runtime_unavailable. terminal.adoptOrphans waits for
unshown placement; reconcile-terminals stays on the critical path. Every
latch opens where its phase finishes today, before RPC starts, so
behaviour is unchanged.
A remote (WSL host or server) pane holds keys typed while it is still
binding its terminal. When that typing cannot safely reach the terminal
the pane binds (startup work or replayed history appeared, the create
bound no terminal, or the pane bound a different terminal than the one
on screen) the hold drops it so it never runs in the wrong shell. That
drop was silent.
The hold now reports a drop of a person's typed keys (not terminal
query replies) made before the bind, and the pane shows one non-blocking
notice per tab: "Some keys typed while the terminal was starting were
not sent." A closed pane reports nothing. Localized in all 11 locales.
* fix(extensions): bound outstanding native-host writes
* fix(extensions): evict the largest stalled native host and allow two queued frames per host
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
The lane showed they never let the daemon fork land earlier in a way that
moved readiness (#35514 measured an earlier fork with no ready-time gain),
so they only add two loop turns to startup.
Two shared matchers recognized the same gate refusal by text:
isWslDistroNotRunningRefusal (beside the message builders) and
isWslDistroRefusalError. The detected-agents refresh now uses the
former and the duplicate module is deleted. Its IPC-prefix test moves
onto the surviving matcher.
The surviving pattern now matches any remedy wording after the sentence,
as the deleted one did, so a refusal phrased differently is still kept
quiet.
* fix(runtime): a device removed on disk stops authenticating at the next check
Auth checks (token validate, device-key lookup, per-request principal
recheck) now stat orca-devices.json and, when it changed since this
process last read it, re-read and merge it the same way a save does.
An unchanged file costs one stat; an unreadable or corrupt one changes
nothing.
* fix(runtime): check the device file before handing out a pairing offer
A pending offer an admin removed on disk is never shown as a QR. The
auth-time re-read also skips while this process's own quarantine is the
reason the file is missing.
Load the Hermes plural-rules polyfill from the phone app entry instead of
mobile-i18n, and transform mobile-i18n and its catalog loader without the
Expo tsconfig in root vitest.
* feat(wsl): re-key the profile of UNC Windows-drive projects converting to drive paths (D1c-4b)
* fix(wsl): keep the conversion's field table out of the main layout-writer census
* fix(wsl): merge the drive-keyed rows startup wrote for a UNC drive project instead of refusing
WSL Git lists a UNC /mnt/c project's folders under their drive paths, so startup discovery writes a
bare C:\-keyed worktree row (and its identity alias) beside the saved UNC one. The re-key then
landed both on one key and every conversion was refused as wsl_drive_unc_key_collision.
Before the re-key, a drive-keyed row holding only discovery bookkeeping is dropped with its alias
and identity row, so the saved row (instance id, name, links) takes the key. Any other two keys
that meet merge when one is empty or both match; only differing user data refuses, and the error
names the key.
* fix(wsl): treat a chosen workspace status as user data, so the conversion never drops it as a discovery row
Discovery writes only the default status. A drive-keyed row with any other status now counts
as the user's, so the conversion refuses with the key instead of dropping it.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl): read /mnt/<letter> with the same case rule as the UNC path folding
Only a lowercase /mnt is drvfs, with the drive letter in either case, as
foldWslUncPathCaseInsensitiveParts already says; /MNT is an ordinary Linux folder.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl): a refused duplicate spelling no longer blocks the drive folder's one converted project as an overlap
* test(wsl): a project nested under a refused duplicate's folder still converts
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>