* Revert "Keep same-machine folders with matching Git remotes in separate projects (#32272)"
This reverts commit 29a5f89b8a.
On a machine with several clones of one repo, #32272 moved the oldest clone
into the shared cross-host project. Shared Windows QA machines have several
orca clones, so everyone's sidebar "orca" project suddenly gained that
machine's main checkout and its worktrees. Revert until the intended
grouping is decided.
Conflicts resolved against later main: Settings files moved into
settings/repository (imports adjusted), and two test files later deleted on
main stay deleted.
* Use checked types in restored test fixtures
* Combine restored fixture type imports
resolveEditorOwnerRoute spread the full app state on every owner lookup;
project only route inputs, cached per state. Compare worktreesByRepo by its
owner fields so sort-order/unread/activity patches skip editor ownership
invalidation.
Linear-provided titles, states, assignees, projects, labels, workspace
names and error messages were printed verbatim, so remote text could
emit OSC/CSI/C1 sequences or forge extra output lines. Move terminalSafe
into src/shared/terminal-safe-text.ts, add terminalSafeLine (collapses
CR/LF), and apply it to every Linear text field in the human-readable
formatters. --json output is unchanged.
Fixes#20478
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* chore(i18n): add a detector for catalog keys no source references
A key can leave the code without leaving the catalog: a feature is reverted, a
setting is rewritten in place, a component is deleted. Nothing fails, so the
entries stay. Every locale carries them, translators spend effort on strings no
screen renders, and coverage numbers understate how much is actually translated.
`verify:localization-extraction` already reports an unreferenced set, but it
cannot be deleted as-is: i18next-cli does not see a key held in a data structure
(`{ key: '…' }`) or passed as a variable, and 18 files do exactly that. So this
requires two independent conditions — extraction misses the key AND the exact
string appears in no source literal under src/ or mobile/.
Two properties matter and both were found the hard way:
- Plural variants are judged by their base key. i18next appends the CLDR
category at runtime, so `…_one` never appears in a source file even though the
base does. Without this the scan eats every plural in the catalog.
- The scan is iterative. Removing a base key can strand the variants that
survived on its account, so it must be re-run until it reports none.
Registered as `verify:localization-orphans` but deliberately left out of the
lint chain: it reports 1,401 keys on current main, so gating now would fail CI.
The prune and the gate come together in a follow-up.
* test(i18n): cover the orphan detector's two non-obvious rules
Both rules decide whether a key is live, and both are easy to break without
noticing: the plural rule keeps `…count_one` when only `…count` appears in
source, and the literal scan is the sole evidence for keys extraction cannot
see — held in a data structure or passed as a variable.
Extracting them as pure functions changes no behaviour: the detector still
reports 14,459 keys, 12,887 referenced, 1,401 orphans, and --check still
exits 1.
* chore(i18n): rename the detector's key pattern constant
main gained the anti-slop rule no-shape-in-symbol-names after this branch
was cut, and KEY_SHAPED trips it once the branch is merged with main.
Renamed to CATALOG_KEY_PATTERN; no behaviour change.
* chore(i18n): name the orphan detector like the other localization verifiers
CODEOWNERS routes /config/scripts/*localization*.mjs to the localization owner, and find-orphaned-catalog-keys.mjs matched neither that pattern nor *locale*, so a change to it asked nobody for review. The file now carries the name of the npm script that runs it, verify:localization-orphans.
* fix(i18n): report stranded plural variants in the same pass as their base key
isKeyReferenced kept a plural variant alive whenever en.json still carried its base key, even when no source reached that base key. The base key was reported and the variants were not, so a prune needed a second run to find them. Only a base key that extraction or a source literal references keeps its variants now; the --check message drops the re-run advice and names the override tables, which sit outside the scan roots.
* fix(i18n): let the literal scan accept every key-shaped literal
The scan captured only literals of seven characters or more and then applied CATALOG_KEY_PATTERN, which has no such floor. A live key shorter than that, held in a data structure, would have been reported as an orphan. en.json has no key that short today, so the result is unchanged; the capture now leaves the shape decision to the one pattern.
* fix(i18n): stop the orphan scan when a source root cannot be read
collectSourceFiles swallowed every readdir error, so a directory it could not read contributed no literals and a key named only there would have been reported as safe to delete. Only a missing mobile/ root is skipped now, since a checkout without the mobile app is still a complete desktop scan; any other error stops the scan.
* style(i18n): wrap one line of the orphan detector to the formatter's width
* chore(i18n): fold the orphan-key detector into the extraction check and retire 1,644 unreferenced keys
verify-localization-extraction.mjs now filters its own unreferenced set by
source literals (src/ and mobile/), plural base keys and dynamic prefixes
composed inside translate calls, and fails when any key is named nowhere.
That replaces the standalone verify-localization-orphans.mjs script and its
second extraction pass.
The prune is recomputed on current main: 1,644 keys leave all seven locale
catalogs, 285 stale entries leave the key-override tables, and
en-runtime-required.json is regenerated.
Supersedes #19801.
Co-authored-by: Evgenii <413011+smwbev@users.noreply.github.com>
* chore(i18n): scope the unused-key scan to src/ and require a dotted dynamic prefix
mobile/ edits do not trigger the extraction check, so literals there must not
decide its verdict; a dotless runtime-built prefix would keep unrelated keys alive.
Co-authored-by: Evgenii <413011+smwbev@users.noreply.github.com>
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Evgenii <413011+smwbev@users.noreply.github.com>
Packaged macOS builds now append /opt/local/bin and /opt/local/sbin to the
seeded PATH when the default-prefix `port` binary exists, and the agent CLI
install-dir fallback also checks /opt/local/bin. Both are appended last so
existing Homebrew/system resolutions never change.
Co-authored-by: Neil <neil@stably.ai>
Each openFiles change (e.g. a dirty toggle) re-resolved every tab's owner
route twice. Cache owner keys per tab until an owner input changes, so a
single-tab update resolves only that tab.
dsh-tui 0.14+ boots the backend last picked in its launchpad
(~/.dsh-tui/kernel.json), so a plain `dsh-tui --resume <id>` could hand
a DSH session id to the Claude or Codex backend. Pin --backend=dsh in the
resume argv; dsh-tui <0.14 skips the joined form, but would read a
separate `dsh` token as the initial prompt.
Fixes#27944
* ci: restore main's Vitest transform cache in PR unit shards
Main produces per-shard transform caches on a schedule; PR shards restore
the newest one for the same config/lockfile/runtime policy. A Vitest cache
key generator folds each literal specifier's resolution candidates into the
per-module key so newly added files that shadow a resolution invalidate the
importer, and bails on glob-expanded dynamic imports.
* ci: list the resolution key plugin in the node tsconfig project
* ci: accept Vitest's CACHEDIR.TAG when sealing the unit module cache
Vitest 5.0.5 tags its cache root, which made every seal fail with
'invalid cache entry name' so no producer cache was ever saved.
* ci: reseed the unit module cache when any cache-key input lands on main
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
The contracts count operations and retained objects; wall-clock budgets
stay in pnpm test:perf:benchmarks, which no CI job runs. They do not
need Blacksmith's consistent hardware, and the PR path filter runs them
~40 times a day ($7.90/day, $5.50 of it on the 6-vCPU Mac).
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Each 12-vCPU arch job spends ~3 of its ~7 minutes waiting on Apple's
notary service; packaging itself runs at the same speed as the hourly
build's 6-vCPU runner. Halve the per-minute rate.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
electron-builder's temp-file cleanup registers async-exit-hook, whose
beforeExit handler calls process.exit(0). The overlap script reported
failures through process.exitCode, so a thrown build exited 0: the
retry wrapper logged success and skipped its second attempt (hourly run
40608643418). Exit explicitly instead, as electron-builder's own CLI
guards against the same hook.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
- Warm electron-builder-linux-<versions> and the hosted ubuntu-24.04 Electron native modules
- PRs restore hosted Linux Electron native modules without saving
- Replace mobile verify's bundler-cache with a restore of a warmup-saved bundle
- Drop the pnpm store cache where a direct install was faster (mobile main, glibc-floor containers)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
The single-file oracle cannot be split by whole-file balancing, so pass
Playwright test-level shards to the changed-spec lane. It was the last
required check in 40 of 42 measured runs.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Scripted guest codex (app-server) and grok (ACP) fixtures, installed on the
guest login PATH beside claude. orcad streams a partial reply, asks for an
approval and takes Stop for each; the lane checks the guest wrapper ran the
fixture with the guest runtime, and that Windows-side codex/grok decoys on
WSL's appended PATH never ran.
Co-authored-by: m4air <m4air@Mac.localdomain>
Node 26.12 bundles SQLite 3.54, which rejects ALTER TABLE ADD COLUMN for
rowid aliases; rebuild the shadowing table with CREATE TABLE instead.
Node 26.10 binds explicit undefined as NULL. The deleted test only pinned
node:sqlite's own validation (a parity guard for the removed Bun adapter);
SqliteBindings already rejects undefined at compile time.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Each lifecycle step now resolves when the stub prints its marker (ready,
booting:, runtime-exit:N) or rejects with diagnostics when the launcher's
output closes first, so a slow macOS Intel runner only makes a step later.
* test(native-chat): name the stalled step in the live-history watcher test
A CI run hit the 30 s test timeout with no step named. Each wait now has its
own 8 s deadline that reports the stalled step with what the watcher had
delivered, and the file sets IS_REACT_ACT_ENVIRONMENT so act() flushes.
* test(native-chat): share one 27 s budget across the live-history waits
Per-step 8 s deadlines were stricter than the old 30 s test timeout; a shared
deadline keeps the total unchanged and only improves the failure message.
* test(native-chat): use an AbortSignal budget instead of the wall clock
Date.now trips the flaky-test wall-clock rule; AbortSignal.timeout gives the
same shared 27 s budget without reading the clock.
* fix(runtime): accept Muse's composer when the voice-input label is absent
Muse labels its composer's top rule "Voice input" only where voice is
available, so on Linux the readiness predicate never matched and
worker-start timed out at agent_readiness. Keep the voice-label match and
also accept the composer itself: a rule (two or more rule chars) directly
before the prompt glyph after the latest Muse banner, so a shell prompt like
'~/app ❯' or '╰─❯' under a printed banner is still not ready.
Fixes#25005
Co-authored-by: j33pia <242526320+j33pia@users.noreply.github.com>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
* fix(runtime): escape Muse composer regex so the orcad bundle stays ASCII
---------
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: j33pia <242526320+j33pia@users.noreply.github.com>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>
* test(pty): scope the group-reap test's ps to its own PTY
The helper listed every host process with full command lines, which
overflows execFileSync's 1MB buffer on busy machines (ENOBUFS). Discover
the tagged job on the test PTY's terminal and recheck survivors by their
captured pids, so the output stays small by construction and a job that
lost the hung-up terminal is still caught.
Refs #34410
* test(pty): clean up a job that discovery never captured
Refs #34410
* fix(daemon): read the PTY process table without blocking on terminal kill and resize
The terminal daemon force-killed a session by running ps and reading
/proc/<pid>/stat synchronously, and checked the resize foreground group
with execFileSync('ps'). A slow or wedged ps froze every terminal the
daemon serves for up to the ps timeout on each call.
Both paths now use the async process-table read (runProcess with a
timeout that kills the child). SubprocessHandle.forceKill may return a
promise; the termination controller awaits it so a refused signal still
releases the claim for a retry. A force-kill or SIGWINCH whose root
exits while ps runs signals nothing, since the captured groups could
already be recycled. ConPTY force-kill stays synchronous so dispose()
still sees the node-pty kill it must not repeat. The in-process
provider's app-quit sweep keeps the blocking variant. The ps dialect
and table readers move to posix-pty-process-table.ts.
Refs #34410
Refs #33870
* fix(daemon): address review on async PTY process-table reads
Guard only the root-pid fallback on exit so proven groups are still swept
after dispose, wait out at most one ps dialect probe, move the sync-launch
baseline entry with the table reader, and keep tests cross-platform.
Refs #34410
Refs #33870
* fix(daemon): keep the PTY open until an in-flight force-kill sweep finishes
A sweep whose root exits mid-read must not signal: the freed TTY may
belong to another terminal. Dispose therefore defers destroy() until the
sweep settles, so its hangup cannot end the root first.
Refs #34410
Refs #33870
* test(daemon): build the destroy-tracking PTY mock without a type assertion
Refs #34410
Refs #33870
* fix(pty): make the local graceful-deadline force-kill async and stub ps in tests
The in-process provider's graceful-deadline timer now uses the async
group sweep; the app-quit sweep and the root-signal path stay
synchronous. Tests that drove force-kill through fixture pids now stub
the async process-table read instead of querying the host, and a new
test pins that waiters do not chain behind a failed dialect probe.
Session ids are only read for rows on the root's terminal.
Refs #34410
Refs #33870
* fix(pty): hold PTY teardown until an in-flight force-kill sweep settles
Orphan cleanup could destroy a local PTY while the graceful-deadline
sweep was still reading the process table, neutralizing its fallback
kill and hanging up the root first. Both the daemon handle and the
local provider now defer teardown through PendingPtyGroupSweep.
Concurrent force-kill callers in the daemon now await the shared
in-flight request, so a joiner's physical-exit budget starts after the
signals are sent and it sees their rejection.
Refs #34410
Refs #33870
* fix(pty): let disposal and orphan cleanup join the in-flight force-kill sweep
Session disposal now joins the controller's shared force-kill request
instead of starting a second sweep. Local orphan cleanup decides its
escalation only after a pending graceful-deadline sweep settles, so a
refused sweep is escalated before the handle is released rather than
leaving the retry with a neutralized kill.
Refs #34410
Refs #33870
* fix(daemon): SIGKILL live PTY groups before a shutdown deadline exit
The old synchronous sweep sent its signals before the shutdown deadline
timer could run; the async sweep can still be reading the process table
when it fires. The deadline now SIGKILLs each live session's process
group synchronously before exiting (node-pty roots lead their own
group), falling back to the root pid when the group kill is refused.
POSIX only.
Refs #34410
Refs #33870
* chore(ratchet): gate event-loop-blocking calls in the daemon, PTY and orcad code
The terminal daemon and orcad serve every pane from one event loop, so a
synchronous child launch or file write there freezes all of them. A new
count ratchet tracks execFileSync/spawnSync/execSync, runProcessSync,
appendFileSync and writeSync per file under src/main/daemon,
src/main/pty, src/main/runtime and src/main/orcad; counts may only go
down. It reuses the sync-spawn ratchet's import-aware call collector,
now extracted to gated-call-use-collector.mjs, so aliases, namespace
imports and re-exports count and same-named methods on other objects
do not.
Refs #34410
Refs #33870
* fix(wsl): background repo probes, PR refresh and worktree scans never start a stopped distro (E9)
Any Windows touch of a \\wsl.localhost path, or Git/gh run for it, boots a
stopped distro. These background paths ran without asking the stopped-distro
gate: the worktree scan refresh (Git-admin fingerprint reads plus git worktree
list), the remote-identity and username enrichment sweeps, the startup
upstream backfill, the quiet-period ref maintenance, and queued PR refreshes.
Each now asks blockedWslDistroForPath first and skips, keeping known data,
until the distro runs; a host path never asks the gate.
* fix(wsl): a user act's wake allowance ends with the act
The allowance lived in an AsyncLocalStorage store, so any timer, listener or
queue created during a user act (opening a workspace, a wake read, the first
git status) kept it forever and could start the distro later, after a
terminate. The scope now carries an active flag cleared when the act settles.
* fix(wsl): skill discovery and automatic refreshPRNow never start a stopped distro (E9)
- Skill discovery (mounted by passive UI) leaves out roots on a stopped or
unverifiable distro, and skips plugin discovery for such a cwd, instead of
reading \\wsl.localhost; they are not reported missing, so the next scan
picks them up.
- refreshPRNow skips automatic reasons (visible, active, post-push) on a
blocked distro like the drainer does; only a manual refresh may start it.
- Ref maintenance's opt-out probe takes the same sync wslDistroForPath
early-out as the other paths.
- Tests: stopped-with-no-cache worktree scans answer from stored rows, and
'unverifiable' is treated as blocked.
* fix(wsl): skill discovery for a WSL target never starts a stopped distro (E9)
Passive UI discovers skills for projects whose agents run in WSL. The target
resolution probed the distro home (wsl.exe) and the scan ran in the distro,
so either booted a stopped one. Discovery now refuses a stopped or
unverifiable WSL target before the home probe and answers no skills and no
sources, so nothing reads as missing and the next scan retries. Delete and
share keep resolving without the gate.
* fix(wsl): a settled user act marks its distro woken; host skill scans never await the gate
- runAsWslUserAct (the gate's wrapper) notes the distro woken inside the act
when it settles, success or throw, so a follow-up is never answered by a
'stopped' read taken before the act.
- Skill discovery takes sync early-outs: a host-only root set and a host cwd
never await the WSL gate.
* fix(wsl): Share discovers as a user act; a stopped distro's skills read as unknown, not absent
- skills:prepareShare (and the skills.share RPC) discover as a user act on the
target's distro, so an explicit Share on a stopped distro finds its skills.
- A blocked WSL target answers an 'unavailable' source per requested kind, and
roots (or a workspace plugin folder) on a stopped distro are reported
'unavailable' instead of dropped, so the installed-skill verdict says the
status may be incomplete rather than offering Install.
* test(wsl): use a fixed fetchedAt in the stopped-distro PR refresh test
* fix(wsl): Share scans stopped distros' skill folders, with or without a target
The desktop Share dialog sends no target, so the user-act scope had no distro
and the gated scan reported a stopped distro's repo folders unavailable; Share
then failed with skill-share-source-not-found. discoverSkills takes
gateStoppedWslDistros: false for both share paths (IPC and RPC), skipping the
stopped-root split and the plugin-cwd check; passive discovery keeps the gate.
Gated and ungated scans of one target no longer share a coalesced answer.
* fix(i18n): translate missing ko/ja/es/fr/pt UI strings
Fill every en.json key missing from the ko, ja, es, fr and pt catalogs
(293 ko/es/fr, 244 ja, 316 pt leaves; purely additive). The provider
credit-balance status-bar keys stay sparse per
provider-credit-balance-locales.test.ts, and zh is left to #34478.
Also list Français and Português (Brasil) in the Settings docs.
Closes#34513
* fix(i18n): keep goal-change failure wording in English until reviewed
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(wsl-host): chat and text generation never run a Windows-side agent CLI on a WSL host
* fix(wsl-host): a Windows-only claude or codex reads as not installed to every CLI caller
* fix(wsl-host): say a Windows-only claude or codex is installed only on Windows at sign-in
* test(wsl): move the lane's chat envelope out of the contract to break an import cycle
* test(native-chat): inject guest mounts into the stock lookup the WSL guest-path tests exercise
A throwing onFailure on a terminal reopen failure would have replaced the
revision conflict the caller sees; it is now caught and logged like the
writer connection's own report. A reopen success also clears a pending
backoff bypass so it cannot carry into a later backoff.
* fix(daemon): read the PTY process table without blocking on terminal kill and resize
The terminal daemon force-killed a session by running ps and reading
/proc/<pid>/stat synchronously, and checked the resize foreground group
with execFileSync('ps'). A slow or wedged ps froze every terminal the
daemon serves for up to the ps timeout on each call.
Both paths now use the async process-table read (runProcess with a
timeout that kills the child). SubprocessHandle.forceKill may return a
promise; the termination controller awaits it so a refused signal still
releases the claim for a retry. A force-kill or SIGWINCH whose root
exits while ps runs signals nothing, since the captured groups could
already be recycled. ConPTY force-kill stays synchronous so dispose()
still sees the node-pty kill it must not repeat. The in-process
provider's app-quit sweep keeps the blocking variant. The ps dialect
and table readers move to posix-pty-process-table.ts.
Refs #34410
Refs #33870
* fix(daemon): address review on async PTY process-table reads
Guard only the root-pid fallback on exit so proven groups are still swept
after dispose, wait out at most one ps dialect probe, move the sync-launch
baseline entry with the table reader, and keep tests cross-platform.
Refs #34410
Refs #33870
* fix(daemon): keep the PTY open until an in-flight force-kill sweep finishes
A sweep whose root exits mid-read must not signal: the freed TTY may
belong to another terminal. Dispose therefore defers destroy() until the
sweep settles, so its hangup cannot end the root first.
Refs #34410
Refs #33870
* test(daemon): build the destroy-tracking PTY mock without a type assertion
Refs #34410
Refs #33870
* fix(pty): make the local graceful-deadline force-kill async and stub ps in tests
The in-process provider's graceful-deadline timer now uses the async
group sweep; the app-quit sweep and the root-signal path stay
synchronous. Tests that drove force-kill through fixture pids now stub
the async process-table read instead of querying the host, and a new
test pins that waiters do not chain behind a failed dialect probe.
Session ids are only read for rows on the root's terminal.
Refs #34410
Refs #33870
* fix(pty): hold PTY teardown until an in-flight force-kill sweep settles
Orphan cleanup could destroy a local PTY while the graceful-deadline
sweep was still reading the process table, neutralizing its fallback
kill and hanging up the root first. Both the daemon handle and the
local provider now defer teardown through PendingPtyGroupSweep.
Concurrent force-kill callers in the daemon now await the shared
in-flight request, so a joiner's physical-exit budget starts after the
signals are sent and it sees their rejection.
Refs #34410
Refs #33870
* fix(pty): let disposal and orphan cleanup join the in-flight force-kill sweep
Session disposal now joins the controller's shared force-kill request
instead of starting a second sweep. Local orphan cleanup decides its
escalation only after a pending graceful-deadline sweep settles, so a
refused sweep is escalated before the handle is released rather than
leaving the retry with a neutralized kill.
Refs #34410
Refs #33870
* fix(daemon): SIGKILL live PTY groups before a shutdown deadline exit
The old synchronous sweep sent its signals before the shutdown deadline
timer could run; the async sweep can still be reading the process table
when it fires. The deadline now SIGKILLs each live session's process
group synchronously before exiting (node-pty roots lead their own
group), falling back to the root pid when the group kill is refused.
POSIX only.
Refs #34410
Refs #33870
* fix(runtime): re-list a repo's worktrees when repo.update changes visibility
`orca repo set --external-worktree-visibility show` (and any other repo.update
client) only emitted repos:changed, so the host renderer kept stale
`visible: false` worktree rows and the newly shown worktree vanished from the
sidebar. The settings controller now also emits worktrees:changed for the repo
when a visibility input changes, without invalidating cached Git scans.
Fixes#32764
Co-authored-by: Younghwan Chae <chyohw97@gmail.com>
* fix(runtime): declare notifyWorktreesChanged on RuntimeConstructionServices
The repository settings controller is built in OrcaRuntimeWithPreservedBranchCleanup,
but notifyWorktreesChanged is implemented further down the chain in
OrcaRuntimeWithGetStatus, so the base class needs the abstract member to typecheck.
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(i18n): give each reused localization key its own sentence
Thirteen keys were shared by call sites that say different things. The
catalog holds one value per key, so the second site rendered the first
site's text: "Move X left" on the right-move button, "Use X repo color"
on the icon picker, "lines above" on the expand-below control, "Open X
issues" on the open-in-Linear action, and similar for status icons,
reclaimable workspaces, remote uploads, automation destination notices,
the add-project button and MiniMax credential errors.
Split every divergent site onto its own key with translations in all
locales, sync the runtime-required catalog, and make
verify-localization-catalog reject a key reused with different fallback
text so the collision cannot come back.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(i18n): pluralize the remote upload toast with i18next count
The toast passed an English "s" as {{value1}}, so ja/ko/zh rendered
"ファイルs", "파일s" and "文件s". The catalog gate requires every locale to
keep the source placeholders, so dropping {{value1}} per locale is not an
option; use count plurals (_one/_other) like nativeDropTempCopyFailed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: add one-line docstrings to the functions this change touches
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(i18n): reject reused fallback keys before --fix writes en.json
A missing key used with two different fallback sentences had its first
sentence written to en.json before the reused-fallback check failed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Matheus Breguêz <matbrgz@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(i18n): translate the main-process startup and recovery dialogs
The GPU safe-graphics prompts, the profile-state recovery and write-failure
dialogs, the startup failure box and the local-build switch built their copy
from English literals, so they stayed English in every UI language. They now
read it through translateMain(), with entries in all six catalogs.
The GPU prompts built their options once at import; they now build them when
the dialog opens, so the language active at that moment applies.
Some of these dialogs can open before main i18n finishes initializing. Until
then translateMain() returned its fallback with {{placeholders}} unfilled; it
now fills them from the options, so the pre-init English copy stays correct.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(i18n): keep main-i18n out of the profile-state dialog's Bun test graph
The profile-state recovery dialogs imported translateMain directly. Their unit test runs in the Bun node-server suite, so that import pulled main-i18n, Electron's app and the renderer locale catalogs into the node-server build graph. config/scripts/node-server-change-scope.test.mjs forbids renderer files there, and the Bun build also failed on the duplicate "sessionNotRestored" key the es/fr/ja/ko/zh catalogs already carry.
The dialogs now take the translator as a dependency, like showMessageBox. src/main/index.ts passes translateMain; the dialog test passes an English translator that fills the {{time}} placeholder the way translateMain does before i18n loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(i18n): read fallback placeholders by index instead of Reflect.get
anti-slop's no-reflect-get flags the placeholder lookup in interpolateFallback. i18next's TOptions already carries a string index signature, so a typed `options[name]` read into `unknown` replaces Reflect.get with the same behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(i18n): finish localizing the main-process recovery dialogs
- Translate the profile repair dialog main added (chooseProfileStateRepair),
including its section labels; CLI and log callers keep English labels.
- Use liveTerminals_one/_other plural keys for the local-build terminal count.
- Retranslate the recovery-not-applied title after main changed its copy.
- Add pt catalog entries for every new key.
- Keep only the pre-init interpolation test; drop the test that pinned
Spanish catalog strings.
Co-authored-by: outof-place <contact@outofplace.space>
* fix(i18n): keep en-runtime-required.json to main plus the new plural keys
* fix(i18n): localize startup dialogs that fire before settings load
Profile-state recovery, preflight failure and recovery-not-applied dialogs
can show before the settings store sets the main UI language. Resolve the
system language first so they no longer fall back to English.
* fix(i18n): keep the uncached pre-ready language read inside the guarded lookup
* fix(i18n): keep the macOS/Windows preflight failure exit synchronous
---------
Co-authored-by: outof-place <contact@outofplace.space>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Hosted macos-15 fails afterPack's packaged x64 terminal host boot check
(hourly run 40608643418: timed out waiting for the PTY), so no
latest-mac.yml is published. Windows, template qualification and routing
jobs stay on hosted runners.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
* ci: narrow native, launcher and packaging cache keys and stop PR-local saves
* ci: hash Rust crate sources, not build outputs, in launcher and hook keys
* ci: fail closed on unlocked native packages and cover transitive key inputs
* ci: resolve the Windows Rust cache scope with a script, not a nested local action
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(i18n): render revised UI copy and fail on fallback drift
en.json kept stale values for 47 translate() keys whose source fallback was
rewritten, so the revised copy never rendered in any language.
- Adopt the newer source copy into en.json and retranslate zh/ko/ja/es/fr/pt.
- Move copy whose meaning changed to new keys (artifacts sign-in, remote host
access link / name, ssh config picker, jump palette automation label, Claude
subagent navigation hint) so stale translations cannot survive.
- Split five keys shared by different sentences at the sites the extractor
flagged (activity group-by, automation destination notices, Linear open
action, workspace options label).
- Fix catalog/source mistakes: onboarding "notify you know" typo, crash report
double dot and missing space, mobile search keyword "app", remote server
restart warning repeating "live tabs", and stale source fallbacks where the
catalog was right (Ghostty import, Skills button, too-many-changes banner,
card style and chat UI descriptions).
- verify-localization-extraction now fails on fallback drift and prints the
en.json and source values with how to fix it.
Closes#25572
* fix(i18n): translate es live tab/pane counts used in server restart warning
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Retained sidebar rows were unconditionally inert, so an idle agent whose
live status was dropped could not be navigated to even though its terminal
was still running. Gate activation on the runtime PTY map for the exact
stable leaf (stablePaneHasLivePty) so sleeping panes, whose layout bindings
survive sleep, stay inert and are never resumed implicitly.
Fixes#20803
Co-authored-by: Neil <neil@stably.ai>
* fix(i18n): select whole count sentences at twelve more call sites
Twelve strings were a noun plus a plural ending passed as an
interpolation value, so every non-English locale got a Latin "s" glued
to its own noun whenever the count was not one. Each call site now
chooses between two whole sentences with a single {{count}}, the same
Singular/Plural pattern #27907 uses for three Settings strings.
ja, ko and zh drop the stray ending. es and fr keep what they rendered
for one and get grammatical plural sentences. The "commits ahead"
tooltip in the compare summary takes the wording the catalogs already
have for the identical sentence, because three translations treated the
ending as an object. Legacy keys stay in the catalogs.
Fixes#27973
* fix(i18n): name commits in Korean in the compare-summary tooltip
The Korean pair for the "commits ahead" tooltip was copied from
compareBaseCommitsAheadOne/Other, which keep the English "commit" and
"commits". Both sentences now say 커밋 with the 개 counter, the form
#27907 uses for its Korean count sentences, so the tooltip no longer
carries an English plural.
The locale test also requires ja, ko and zh to use one sentence for
both counts. It fails on the previous Korean pair.
* fix(i18n): take the Korean count sentences from the audit review
Eleven of the twelve Korean pairs still put the number before the noun
with no counter ("3 라벨"), the order left over from dropping the
English plural ending. They now read noun, number, counter
("라벨 3개", "멤버 3명"), the wording abruption supplied in review of
#27974. The "commits ahead" pair already had that form and is unchanged.
Three sentences are also reworded to follow the English: a failed
discard no longer says the files could not be deleted, the group toast
says how many of the contained projects remained, and "restricted key"
is spelled out as an API key with restricted access.
The 22 values are recorded in locale-ko-key-overrides.json, next to the
other reviewed Korean strings.
* chore(i18n): credit the Korean count sentences to their reviewer
The Korean wording applied in f6bbcaf341 was supplied by abruption as a
patch in review of #27974 and went in byte for byte. That commit could
not carry a co-author trailer, so this empty one does.
Co-authored-by: YEONGUK LEE <contact@abruption.dev>
* fix(i18n): drop the twelve keys the count sentences replaced
No call site reads the twelve noun-plus-ending keys any more, so they
leave the six catalogs, as #28185 did for the keys it replaced. With
them go the three Korean override entries for those keys and the one
value ko-ui-semantic-mistranslations.test.ts pinned for the old
sshImportSynced text; the sentence it guarded is now the
sshImportSyncedSingular/Plural pair, covered by
count-sentence-locales.test.ts. en-runtime-required.json is
regenerated.
* fix(i18n): give remote drop uploads their own count keys; drop low-value tests
- Remote terminal drop uploads used the runtime keys with a different
fallback ('to remote…'); split them into uploadingFileToRemote{Singular,Plural}
with translations in all catalogs.
- Remove mock-translate tests and the catalog-pinning count-sentence test;
verify:localization-* already enforces key and placeholder parity.
- Trim the 'why whole sentences' comments.
- Regenerate en-runtime-required.json.
* fix(i18n): natural zh/ja count strings and es host-import wording
---------
Co-authored-by: YEONGUK LEE <contact@abruption.dev>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(i18n): translate 229 missing Simplified Chinese UI strings
Complete the zh-CN catalog for recently added surfaces: browser
history, agent permission settings, worktree/workspace management,
cookie import toasts, SSH host server dialogs and source-control
dropdown states. Terminology follows the existing catalog.
11 status-bar credit/balance keys stay untranslated on purpose:
provider-credit-balance-locales.test.ts keeps target catalogs sparse
and interpolates English copy at runtime.
Fixes#34439
* fix(i18n): translate remaining Simplified Chinese strings and unify workspace sleep wording
Add zh translations for the 83 English keys added since this branch was
cut (browser extensions, experimental pane, blocked-page load failure),
leaving the sparse status-bar balance keys untranslated as required.
Use 休眠 consistently for workspace sleep in the sidebar and terminal
close dialog.
* i18n(zh): translate WSL Windows-drive refusal strings
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Shared remote dropdown builders take count/force-push sentence builders
through RemoteDropdownCopy; English defaults keep mobile and older callers
byte-identical. Desktop uses whole-sentence {{count}} plural keys, also for
the primary action tooltip. Refs #34144.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(i18n): localize keyboard shortcut titles, groups, and filters
Shortcut action titles and group names came straight from the English
keybinding registry, so the Shortcuts pane, settings search, conflict
warnings, and the terminal shortcut capture toast stayed English in
every locale. The Status filter labels were also hardcoded.
Add a renderer-side copy map with literal translate() keys that
`satisfies` every static action id, one {{agent}} template for agent
tab titles, and a group map. Localized definitions keep the English
title and group as search keywords so English queries still match.
Plugin titles pass through unchanged, and the plugin keybinding consent
preview keeps English registry titles so language packs cannot relabel
the actions a plugin shadows.
Translate the new strings for zh, ja, ko, es, fr, and pt.
* test(i18n): assert shortcut localization behavior without pinning catalog strings
* style: format keybinding title copy test
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
GitHub issue close/reopen swallowed every gh failure mentioning "already"
(e.g. "API rate limit already exceeded"), and GitLab MR close/reopen did the
same for glab. gh exits 0 when the issue is already in the target state, so
every gh failure is now reported. For glab, only its fixed no-op refusals
("this merge request has been closed" / "this merge request is already open")
count as success; "has already been merged" and all other errors surface.
Fixes#28766
Co-authored-by: Neil <neil@stably.ai>