Commit Graph
628 Commits
Author SHA1 Message Date
Brennan Benson 913509edeb fix(orchestration): prevent slow worker-start stalls (#16300)
* Extend orchestration agent submission timing budgets

* fix(orchestration): preserve mutation recovery identity

* fix(orchestration): preserve recovery executable identity

* fix(orchestration): keep worker starts and recovery commands safe

* test(orchestration): cover federated worker preflight

* fix(orchestration): harden mutation recovery

* fix(orchestration): redact dispatch recovery credentials

* chore: preserve upstream skill dialog formatting

* test(orchestration): stabilize agent prompt submit e2e

* fix(orchestration): validate federated start receipts

* perf(runtime): cache unchanged prompt verification tail

* fix(orchestration): reject worker-start timer overflow

* fix(orchestration): normalize worker-start timeout defaults

* fix(orchestration): normalize worker-start readiness budgets

* fix(orchestration): normalize federated readiness timeout

* test(runtime): tolerate current-main degradation exports

* chore: preserve current-main orcad formatting

* chore: drop unrelated formatting carryover
2026-08-27 15:25:30 -07:00
Jinwoo Hong 249d93bc5d feat(browser-preview): render remote HTML docs locally over an orca-preview scheme (STA-5557) (#16679) 2026-08-27 14:34:21 -07:00
Neilandinnocarpe f400f8fd5f fix(macos): opt out of press-and-hold so held keys repeat (#14746) (#15589)
* fix(macos): opt out of press-and-hold so held keys repeat (#14746)

macOS routes press-and-hold to the accent picker unless an app sets
ApplePressAndHoldEnabled=false for its own bundle, so holding j in vim
inserted one character instead of repeating. Orca never set it.

Written at most once, and never over an explicit value: `defaults read`
is domain-scoped and exits 1 when the key is absent, which is the only
way to tell "unset" from a deliberate false — Electron's
systemPreferences.getUserDefault reports false for both. A recorded
decision in userData keeps a later launch from re-clobbering a user who
deletes the key to get the accent picker back.

* docs(macos): record the revert hazard and CI's macOS test gap

Two things a reader of this module cannot otherwise know.

A revert leaves the key written in every user's domain forever. AppKit reads
the plist, not this file, so removing the code alone keeps press-and-hold
disabled for everyone who ran an affected build. The sibling period-substitution
module carries the same warning because that fix was already lost once this way.

And the real-binary test file that pins the defaults(1) exit-code semantics this
design rests on never runs in CI: the e2e workflow and both unit-test jobs are
ubuntu and windows, and the only macOS runners in the repo are build and
packaging jobs that run no tests. Those six tests plus the real-bundle e2e case
pass on a developer Mac and execute zero times in a green PR, so the comment
should not imply enforcement that is not there.

Refs #14746

* feat(macos): let users turn the accent menu back on (#14746)

Orca disables press-and-hold for its own preferences domain so held keys
repeat. That is the right default, but the way back was a `defaults write`
buried in a source comment: nothing in docs/ or the README mentioned it, and
the preference is per-application, so it silently takes the accent picker
away from the Markdown editor and every other text field too.

Terminal -> Advanced now carries a "Character Accent Menu" switch, macOS and
desktop only. A web client cannot write a macOS preference for the machine the
user is looking at, so the control and its search-index entry are both gated on
that, not on the client's platform alone.

Precedence, which is the part that is easy to get wrong: the setting is
`undefined` until the user touches it, which is what keeps a hand-run `defaults
write` in charge for everyone who never opens the toggle. Once used, Orca owns
the key and writes exactly what the switch asks for -- `ApplePressAndHoldEnabled`
*is* the accent-menu switch, so it maps straight through with no inversion. The
choice is compared against `appliedSetting` in the existing decision record
rather than against the domain, so a `defaults write` made *after* using the
toggle is still the newer choice and survives the next launch. Re-asserting the
value every launch would have reintroduced the clobbering the record exists to
prevent.

The write lands for the next launch, since AppKit reads the preference as the
process starts, so the toggle shows the same restart banner the window-blur
setting uses. That banner is now a shared component, keeping its original
translation keys.

docs/reference/macos-press-and-hold.md records the precedence rules, the
`defaults read` rationale, the revert hazard, and the fact that none of this
executes in CI: every macOS job builds or packages and runs no tests, so the
real-binary and e2e coverage here passes only on a developer Mac.

* docs(macos): stop asserting when AppKit re-reads the press-and-hold key

Five places stated "AppKit reads the preference as the process starts" as
fact. That is the reason given for requiring a relaunch, and it is not
something this change ever measured.

Evidence points the other way: terminal emulators that register this key
after their process has started get key repeat in that same launch, which a
read-once-at-startup model cannot explain.

The relaunch requirement itself still looks right, but for a different and
verifiable reason: the write goes out through a separate `defaults` process,
so this app's own cached copy need not observe it. That is what the comments
now say, with the AppKit question left open rather than answered.

Refs #14746

* docs(macos): correct the startup comment's launch-timing claim

The comment said this call site is "the last point that can still matter for
this launch", which contradicts the rest of the module: the write is assumed
to land for the next launch because it goes out through a separate `defaults`
process. Reported on the PR by @innocarpe, who also supplied the replacement
wording.

Co-authored-by: innocarpe <innocarpe@users.noreply.github.com>

* refactor(macos): probe press-and-hold through the shared spawn chokepoint

`src/shared/child-process/child-process-import-boundary.test.ts` forbids a
direct `node:child_process` import outside its allowlist, and the allowlist only
shrinks — so this module moves to `runProcessSync`, which exists for callers
that genuinely cannot await. This one runs before `app.whenReady()`.

`runProcessSync` returns a non-zero exit instead of throwing it, so the
three-way read decision is re-expressed against `ProcessResult`: exit 0 is an
explicit value, exit 1 is a missing key, and a timeout, a signal kill, any other
exit, or a child that never started all stay 'unknown'. The throw path is now
inside `interpretDefaultsRead` so a spawn failure is reachable from a test
rather than hidden in an untested catch, and the write checks the exit code —
a refused `defaults write` no longer looks like success.

Both boundary-test failures were the same import: with it gone the offender
count returns to 155, so no ratchet baseline is bumped.

* Revert "feat(macos): let users turn the accent menu back on (#14746)"

This reverts commit cc5669f306.

---------

Co-authored-by: innocarpe <innocarpe@users.noreply.github.com>
2026-08-27 13:44:03 -07:00
Jinjing 07b7e9e68d Open target=_blank links and unnamed popups in new Orca tabs (#16720)
* feat(browser): open target=_blank links and unnamed popups in new Orca t

- Treat target=_blank as a new-tab request matching browser behavior
- Route unnamed, featureless window.open() calls to Orca tabs instead of native popups
- Add rate limiting to prevent page-initiated tab loops
- Inherit session profiles when opening links to maintain isolation boundaries

* fix(browser): deny new-tab window.open when renderer is destroyed

Move deny action outside conditional to ensure new-tab intents are
safely rejected even if renderer vanishes mid-open, preventing native
popup fallthrough. Add test coverage and simplify comments.

* Share page-initiated tab budget across opener popup tree

Prevent pages from bypassing the new-tab rate limit by chaining popup
windows. The page-initiated tab quota is now shared by all popups in
an opener tree (root + named children), so child windows inherit their
root's budget instead of each getting a fresh allocation.
2026-08-27 08:49:02 -07:00
Jinwoo Hong 0f522c35e5 fix(remote): gate empty session inventory on host authority (#16546) 2026-08-26 22:30:48 -07:00
Jinjing 9fb5220239 Prevent duplicate file renames when input unmounts after Enter (#16719)
When Enter is pressed to confirm a rename, the input unmounts and its onBlur
handler fires as it detaches from the DOM. Without consuming this event, a
second commitRename call would attempt to rename against the old path. Setting
the cancel flag after capturing the new name causes the trailing onBlur to
return early, preventing the duplicate operation.
2026-08-26 22:17:59 -07:00
Jinwoo Hong d60a3c900b Reset stale terminal modes after dead TUI replay (#16379) 2026-08-26 19:49:26 -07:00
Brennan BensonandSiddiqui Qamar 5a59bc5bc4 fix(grok): stop Orca's Grok hooks from costing anything outside Orca (#16666)
* fix(grok): stop Orca's Grok hooks from costing anything outside Orca

Orca registers Grok agent-status hooks in the global $GROK_HOME/hooks. Grok
loads that directory on every session, so a Grok run that Orca did not launch
still paid for the hook on every event, and Orca rewrote the file even after a
user had emptied it to opt out (#15518).

The registered POSIX command now guards on ORCA_PANE_KEY before doing anything.
That variable is part of the pane identity Orca injects into terminals it
launches, and unlike the port and token it never comes from the endpoint file,
so it is present exactly when the session belongs to Orca. A standalone session
short-circuits without spawning a shell for the managed script at all. The same
guard is applied to the remote install, because a remote host runs standalone
Grok sessions too.

PreToolUse is no longer registered. It is a blocking hook, so Orca sat on the
critical path of every tool call and doubled the per-tool spawns, for a
transition PostToolUse already reports.

Windows cannot use the guard: the command there must be a single spawnable
token, so it is a bare script path with no shell to evaluate a test. For that
case the hooks are removed when Orca quits -- locally, on WSL guests, and on
connected SSH hosts -- and reinstalled on the next launch. A config the user has
emptied is left alone on startup; turning the setting back on in Settings is an
explicit and later choice, so that path reinstalls.

Removal is careful about what it is deleting. It strips only Orca's own entries,
keeps user-authored ones, and deletes the file only when no hook entries remain
-- keying that off the whole object would leave a stray non-hook key behind, and
the emptied-config check would then read that remnant as a deliberate opt-out
and never reinstall. A config the user has symlinked into a dotfiles repo is
written through rather than unlinked, and is exempt from the emptied-config
check for the same reason: after a quit it is a file Orca emptied, not one the
user did.

Writes go through temp+rename. Grok refuses to build a sandbox profile for a
hook JSON with more than one hard link, so publishing by hard link would fail
any session that started during the write.

Install and removal on remote hosts now read the platform from the same field.
They did not, so a Windows remote whose bridge env was incomplete had hooks
installed and never removed.

Co-authored-by: Siddiqui Qamar <137684575+siddqamar@users.noreply.github.com>

* fix(grok): preserve hook state outside Orca

---------

Co-authored-by: Siddiqui Qamar <137684575+siddqamar@users.noreply.github.com>
2026-08-26 12:48:52 -07:00
Jinjing cda2280d63 Show all automations (#16532)
* Add all-host automations with scoped ownership and multi-authority suppo

Enable automations to run on multiple hosts (SSH targets and local) with
owner-fenced mutations, scoped list queries per host, and conflict
resolution. Introduces desktop and runtime authorities as distinct
automation storage owners, with per-host caching, invalidation, and
retry scheduling on the renderer. Captures registration generations for
SSH hosts to survive re-adoption. Adds CLI support for destination
selection and conflict recovery.

* Filter automation create projects by destination host

Only offer projects available on the selected destination, preventing
the mismatches that would fail at submit time. Auto-adjust the project
selection if it becomes unavailable when the destination changes.

* Add runtime storage authority support for automations

- Support both runtime and desktop as automation storage authorities
- Make owner preconditions optional for legacy-client compatibility
- Cache automation list projections to improve performance
- Add per-row repo/worktree resolution for cross-authority collisions
- Extend automation.list RPC to always include owner metadata

* Replace child_process.execFile with runProcess for external automations

- Migrate external-manager to use cross-platform runProcess wrapper per child-process safety policy
- Abstract electron app/ipcMain APIs in orca-runtime via environment accessors
- Install fake app environment in automation tests for consistent setup
- Reorganize imports to use specific module paths (ssh-target-registry, agent-detection, browser-error)
- Remove external-manager from child-process import allowlists (no longer violates direct import)

* Unify desktop automation CRUD onto the local runtime RPC surface

The desktop authority now speaks the same automation.* RPC contract as
remote runtimes, via callRuntimeRpc({kind:'local'}) -> runtime:call ->
the shared RpcDispatcher. The automations:list/listRuns/create/update/
delete/runNow IPC arms, their preload members, and every renderer
desktop-vs-runtime transport fork are retired; the runtime methods are
the single implementation of scoped lists, owner fencing, and change
publication for both transports (mobile clients already exercised them).

The desktop probe scheduler's priority lease survives the move as an
AutomationService hook the IPC registration installs and the runtime
methods take, so Orca's own automation traffic still parks queued
external-manager probes.

External-manager scope arms and dispatch-loop plumbing stay on IPC by
design; automation change events keep their existing channels (renderer
ingestion already converges them by authority).

* Remove automation ghost SSH tombstone scanning

This functionality for synthesizing tombstones for automation-referenced SSH
targets is no longer needed as part of the automation system refactoring.

* Refuse orphan automations at dispatch time, not migration time

Remove migration-time disabling of orphan automations and the `enabledDecidedBy` field. Dispatch now refuses orphans at runtime instead, simplifying state management and UI. Orphans are left unstamped and enabled; dispatch refuses to run them via `resolveAutomationRunTarget`.

* Show all automations in flat table with unified filter menu

- Replace host picker component with comprehensive Filters menu supporting status, last run, agent, and host filters
- Flatten automation list layout to single table instead of host-grouped sections
- Add Host column to display execution host for each automation
- Display active filters as removable pills below toolbar
- Delete unused AutomationHostPicker* components

* Add automation owner fencing and destination validation

- New AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY for owner preconditions; legacy clients get owner metadata snapshotted at RPC boundary for compatibility
- Editor captures and revalidates automation destination before save, preventing silent retargeting if SSH infrastructure changes mid-edit
- SSH target types now isolate renderer-authored fields; generation is server-owned and stripped by IPC handlers

* Route automation recovery actions to the origin host

When an automation action fails due to owner fencing, recovery verbs
("Update server", "Reconnect") must run on the host where the refusal
originated: the row's captured owner for row operations, or the
destination the create dialog captured, not the list's filtered host.

* Remove external manager scope limitation notices

Consolidate create destination eligibility checks with a unified predicate
and fix the bug where desktop repo IDs could be sent to runtime hosts where
they cannot resolve.

* Persist only store-derived automation contexts, not client-perspective o

Store contexts must never be based on client-provided runContext or sourceContext
values—clients speak a different perspective (e.g., 'runtime:<id>' for host IDs
they assign), and persisting those makes the store projection orphan automations
it actually owns. Derived contexts now take precedence in create and update paths,
with explicit null still honored to clear a value. Tests verify this by simulating
drift after storage and confirming that moves re-derive while toggles preserve.
2026-08-26 09:50:12 -07:00
Jinjing 4d2dc0fae5 test: pin cross-version browser placement test to explicit baseline (#16554)
* test: use explicit baseline for cross-version browser placement test

Pin to v1.4.184 to ensure consistent testing against the release
predating client placement. This avoids coupling the legacy-baseline
bump to unrelated schema refactors in newer versions.

* fix(windows): treat inaccessible processes as alive in tests

When checking process state on Windows, EPERM (permission denied) indicates
an inaccessible but live process. Only ESRCH (process not found) proves
exit. Correct isAlive() to distinguish these cases.

Also add windowsHide:true to child process spawns and use explicit SIGKILL
when force-killing the host process.
2026-08-26 00:02:12 -07:00
Jinwoo Hong 868fc39d32 fix(worktrees): refresh paired clients after external discovery (#16557) 2026-08-25 23:18:30 -07:00
Jinjing 933345d347 Clarify upstream divergence stats for rebased branches (#16358)
* Clarify upstream divergence stats for rebased branches

When a branch is rebased, it still tracks the pre-rebase upstream
while comparing against the new base. Move upstream arrows to the
head line to prevent them being confused with compare-base counts.

* Show upstream divergence stats independent of compare base

Measure HEAD against upstream regardless of compare-base state,
so divergence indicators stay visible even when comparison is
missing, loading, or failed. Also use cross-platform temp paths
in tests.

* Show commit counts against compare base, not upstream

Upstream divergence (↑/↓ against tracking branch) was confusing for
rebased branches — the counts appeared beside the base ref but measured
against the upstream branch. Show only the compare base count instead,
on the line that names it.

* Report branch divergence in both directions

Rebased branches are typically ahead AND behind their base; a single count
hides this case. Use symmetric range with --left-right --count to capture
both directions efficiently, then expose commitsBehind in the UI alongside
commitsAhead.

* Use semantic names for i18n keys and template variables

Rename hash-based translation keys to descriptive identifiers and replace generic value0/value1 placeholders with semantic variable names like `count` and `ref`. Improves code maintainability and makes translation strings self-documenting.
2026-08-25 22:19:04 -07:00
Jinjing 07b82340f3 Route terminal file links to sibling workspace tabs (#16544)
* fix: route terminal file links to sibling workspace tabs

Detect when a clicked file is already open in a sibling workspace and route
to that existing tab instead of creating a duplicate. Reorganizes workspace
activation to dispatch by both worktree id and execution host, allowing the
same worktree name across different remotes to be disambiguated and routed
correctly.

* test: validate terminal file link opens in correct sibling worktree

Enhance test to check both file path and active worktree ID, ensuring
the linked file opens in the intended sibling workspace.
2026-08-25 22:17:59 -07:00
Jinwoo Hong c8567eb16e fix(sidebar): preserve hidden rows in manual order (#16488) 2026-08-25 16:46:42 -07:00
Jinwoo HongandJinwoo-H a9781a4118 STA-4150: client-hosted remote browser (consolidated) (#15448)
Co-authored-by: Jinwoo-H <jinwoo@stably.ai>
2026-08-25 15:36:51 -07:00
Jinjing 32df073e44 fix(browser): focus unified tab on browser page palette activation (#16366)
* fix(browser): focus unified tab on browser page palette activation

When activating a browser page from the palette, find and focus the
corresponding unified tab before setting active state. Ensures the
tab group receives focus. Also increase e2e test timeouts to improve
stability on slower runners.

* test(e2e): read latest restored terminal frame

* Fail browser page activation when unified tab is missing

Without a unified tab, the workspace can't render in the pane. Reporting
success leaves the previous tab on screen. Fail the activation to prevent
this confusing state.
2026-08-25 04:00:59 -07:00
NeilandSeongho.Bak fcf55f2d68 fix(terminal): stop Orca mangling the OMP/Pi title it writes itself (#16381)
* fix(terminal): collapse identity group in the title churn signature

Replaces the ingest-time title rewrite from #16373 with a non-destructive
fix at the actual cause.

The churn suppressor `isDecorativeAgentTitleFrameChange` keyed on the
literal label, so `working:OMP` and `working:Pi` compared unequal and every
alternating frame from a wrapped harness committed a store patch. #16373
made the labels agree by rewriting the stored title to the tab's launch
owner — but `runtimePaneTitlesByTabId` is also the Windows Shift+Enter
byte-encoding input, so normalizing at ingest destroyed evidence other
consumers read (fixed separately in #16376).

Collapse the identity group inside the signature instead. Which member of
a group a frame names is decoration, exactly like the spinner glyph the
signature already strips, so frames compare equal without touching what is
stored. Suppression now changes only WHETHER a frame commits, never WHAT
it says.

Also fixes the flap under a multiplexer (#8032): the collapse runs over
wrapper segments, so "zsh | ⠋ Pi" and "zsh | ⠙ OMP" compare equal, which
the anchored owner-relabel in #16373 never matched.

Reverts the store changes from #16373 and drops the helper it added.

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>

* fix(terminal): fold only bare identity frames into the group token

A legacy "π - <session> - <cwd>" title is Pi-compatible too, so folding
every profile match collapsed two different sessions to the same signature
and suppressed the change outright — reintroducing #16093 through the
churn signature.

Fold only exact bare identity frames, matched per wrapper segment, so
semantic session titles keep comparing on their own text.

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>

* docs(terminal): correct the flap diagnosis in the repro header

Verified against the OMP source: it emits only π-glyph frames
(`DEFAULT_TERMINAL_TITLE = "π"`, title-generator.ts:25), and on an
Orca-hosted pane its native titler cedes to Orca's own injected extension,
which writes `⠋ π - <session> - <cwd>`.

So OMP emits neither "OMP" nor "Pi". Both flap sides are Orca's:
"OMP" from driveSyntheticTitleFromHook, "Pi" from normalizeTerminalTitle
collapsing our own extension's output to a hardcoded literal.

The prior header credited the wrapped harness for frames it never sends,
which is the same wrong narrative that produced eight fixes at eight
layers. No behavior change.

* fix(terminal): stop Orca mangling the OMP/Pi title it writes itself

Verified against the OMP source: it emits only π-branded frames
(`DEFAULT_TERMINAL_TITLE = "π"`, title-generator.ts:25), and on an
Orca-hosted pane its native titler cedes to Orca's OWN injected extension,
which writes `π - <session> - <cwd>` / `⠋ π - <session> - <cwd>` at 80ms.

So neither flapping string came from OMP. Orca made both:
  "Pi"  — normalizeTerminalTitle collapsing our extension's output to a
          hardcoded literal, discarding the session name and cwd (#16093)
  "OMP" — driveSyntheticTitleFromHook injecting over it every 80ms

Fixed at the source:
- normalizeTerminalTitle canonicalizes only the rotating braille frame and
  keeps the rest, in both spinner positions and through a multiplexer
  prefix (#8032). Status still round-trips through normalization.
- detectAgentStatusFromTitle reads the π state separator, so `π ! <label>`
  is permission instead of the blanket idle that hid a blocked agent.
- normalizeCompatibleAgentTitleForOwner swaps only the brand for the
  owner's label, so a pane still reads as its launch owner (#6689, #7633,
  #9077) without losing the session text.
- pi/omp set synthesizeWorkingTitle: false — the agent animates its own
  working title. Terminal states still synthesize; they carry the pane's
  agent identity downstream.

Reverts the ingest-time title rewrite from #16373, whose normalization of
runtimePaneTitlesByTabId also changed Windows Shift+Enter bytes (#16376).

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>

* fix(terminal): match the state separator only in exact profile casing

The separator check runs on every title, so `omp - deploy notes` and
`pi - refactor the parser` read as an idle agent. The owner rewrite only
ever emits the exact profile labels, so dropping case-insensitivity keeps
`OMP - tmp` classifying while ordinary prose stops matching.

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>

* test(terminal): pin one real OMP turn to two committed patches

Drives 30 working frames as Orca's injected extension emits them plus the
idle transition, and asserts what survives the churn gate. Before the fix
every frame alternated "⠋ Pi"/"⠋ OMP" and each one committed — ~12 store
patches per second on a working tab.

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>

* fix(terminal): carry the permission guard inside the separator reader

`-` is both a π state separator and the delimiter in the synthetic
permission label, so `OMP - action required` read as idle. It resolved
correctly only because detectAgentStatusFromTitle happens to check the
synthetic label first — and the separator fn is exported, so a direct
caller inherited the bug.

Also pins the owner rewrite's fixed-point property, which holds only
because getAgentLabel does not tokenize omp/pi, and corrects a comment
that overstated how tightly the brand swap is scoped.

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>

* docs(terminal): name the flag the code actually sets

The suite header cited `synthesizeTerminalTitle: false`; the profiles set
`synthesizeWorkingTitle: false`. The distinction is the whole reason the
narrower flag was chosen — terminal-state frames still carry the pane's
agent identity downstream — so the wrong name buried the rationale.

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>

---------

Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
2026-08-25 02:28:08 -07:00
Wooseong Kimandm4air 60a3fd8873 fix(i18n): localize the keep-awake corner chip (#14775)
* fix(i18n): localize the keep-awake corner chip

Route the status-bar keep-awake chip through the shared Agents copy
helpers and add missing locale entries for chip-only words.

Fixes #14490

* test(i18n): restore previous language after keep-awake locale suite

* test(i18n): render component in localization tests instead of static che

Converts the keep-awake localization test from static source-code validation to actual component rendering with React Testing Library, providing more reliable verification that the UI displays correctly across all supported languages. Improves translated descriptions for consistency and accuracy.

* test(i18n): add aria labels and descriptions to localization test

- Adds missing localization keys to test data for Spanish, Japanese, Korean, and Simplified Chinese
- Updates test assertions to verify `ariaLabel`, `onDescription`, `autoDescription`, and `offDescription` are properly translated
- Completes localization coverage for the keep-awake corner chip component

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-08-24 22:46:29 -07:00
Jinwoo Hong c60d2ba895 fix(agent-resume): stop ghost resume tabs after finished turns (#16308) 2026-08-24 19:32:52 -07:00
Neil c83499fc8c Keep sidebar position when deleting active worktree (#16040) 2026-08-24 18:14:12 -07:00
JinjingandBrennan Benson 7b9529da22 Add keyboard shortcut for workspace deletion (#16271)
* Add keyboard shortcut for workspace deletion

Default Mod+Shift+Backspace (⌘⇧⌫ on Mac) lets users delete the hovered
worktree or folder workspace immediately. The shortcut targets the
sidebar hover state rather than requiring focus, and avoids terminal
pane D-based split shortcuts on all platforms.

Co-authored-by: Brennan Benson <brennankbenson@gmail.com>

* Omit delete shortcut from disabled Delete Worktree for primary checkout

- Remove shortcut badge from the disabled "Delete Worktree" action when it cannot be executed
- Only show shortcut in multi-context delete actions where the command is available
- Extract host identity parsing into reusable helper function to prevent inline string manipulation
- Fix folder workspace deletion to use correct host-qualified identity comparison

* Document host extraction safety for destructive worktree ops

Unqualified identities must stay undefined rather than defaulting to
'local'. Destructive operations depend on correct host identification.
Added tests and JSDoc to clarify this safety-critical behavior.

* fix test

---------

Co-authored-by: Brennan Benson <brennankbenson@gmail.com>
2026-08-24 10:12:38 -07:00
Jinwoo Hong c618ec7393 test(reliability): protect recent P0 regression invariants (#16163) 2026-08-24 09:38:46 -07:00
Jinjing f5fd7303ab test(e2e): cover tab-bar agent launches on Windows and WSL (#16110)
* test(e2e): gate the tab-bar agent launcher on Windows shells and WSL

The `+` menu agent launcher had no golden coverage in the Windows lane, so a
Windows-only break anywhere in its chain (detection row, startup-plan build,
tab create, PTY spawn, startup-command injection) could ship unnoticed.

Adds a golden spec that launches a stub agent from the menu and asserts the
agent's own banner reached the pane — a tab that spawned a bare shell instead
is indistinguishable at the store/tab layer. Runs two agents everywhere, and
on Windows also PowerShell, cmd, Git Bash and a WSL project runtime.

* test(e2e): track WSL stub agent staging state for precise cleanup

Refactor `stageWslGoldenStubAgent` to track which artifacts it creates
during setup, then only remove those artifacts during cleanup. This
prevents the test from destructively removing pre-existing symlinks or
state from previous runs, improving test isolation and idempotency.

* test(e2e): track WSL stub agent staging state for precise cleanup

- Back up and restore pre-existing stub agents to avoid destroying them
- Simplify verbose test comments to match project style guidelines

* test(e2e): serialize WSL stub agent setup with distributed lock

- Add mkdir-based lock to prevent concurrent staging invocations
- Reclaim stale locks after 10 minutes to recover from crashes
- Track lock ownership in stage state for safe cleanup

* test(e2e): track WSL stub agent staging state for precise cleanup

Track which stubs this test helper stages by writing a marker file, then
only remove stubs during stale-lock recovery if we created them. Prevents
cleanup from removing stubs left by other processes.
2026-08-24 08:58:19 -07:00
Neil afd76a4df9 fix(terminal): preserve synchronized frames on reveal (#16026) 2026-08-24 00:01:53 -07:00
Neil 95633a7883 Fix stale task-source flashes in new workspace input (#16145)
* fix(new-workspace): prevent stale GitHub URL selection

* fix(new-workspace): guard all task URL transitions

* test(e2e): make task URL frame proof runner-safe

* fix(new-workspace): guard Enter during task URL lookup
2026-08-23 22:18:39 -07:00
Jinwoo Hong 4ee41fede2 fix(automations): reveal full prompt from detail view (#16067) 2026-08-23 19:59:30 -07:00
Jinwoo Hong 0a613d5fed test(e2e): stabilize paired Quick Open large-tree coverage (#16058) 2026-08-23 16:47:12 -07:00
Jinwoo Hong 3d74f5fe03 test(remote): preserve HTML inventory RPC failures (STA-5210) (#16056) 2026-08-23 16:09:49 -07:00
Neil 8af02d658c Preserve code editor selections across tab switches (#16132)
* Preserve editor selections across tab switches

* Defer editor selection caching to tab lifecycle
2026-08-23 14:25:03 -07:00
Jinjing 7a72f341f7 Split pty-connection.ts into focused modules under 400 lines (#15166)
* refactor: split pty-connection.ts under 400 lines

* rm design doc

* refactor(pty-connection): extract reattach payload handlers as factories

- Replace bindApplyReattachPayload with createReattachPayloadHandlers factory that returns handlers instead of mutating session directly, enabling better composability and testing
- Extract waitForUserInitiatedSshConnect as standalone function for reuse across deferred session attach flows
- Create ReattachPayloadSession type to document and isolate required session capabilities
- Add test coverage for overlapping reattach payload attempts
- Clean up comments to remove redundant prefixes (session.pane → pane, session.transport → transport)

* fix(pty-connection): correct sequencing and state bugs in spawn and reat

- Fix terminal tail slice to take prefix instead of suffix, preserving escape
  sequence markers needed by next scan
- Clear pending pane serializer when direct SSH retry PTY is unclaimed
- Initialize interrupt status baseline to undefined so first input advances
  sequence counter
- Bump reattach generation only after confirming current attempt owns the stream,
  preventing superseded results from canceling in-flight prepaint

* fix(pty-connection): correct sequencing and state bugs in spawn and reat

- Fix terminal tail slice to take prefix instead of suffix, preserving escape
  sequence markers needed by next scan
- Clear pending pane serializer when direct SSH retry PTY is unclaimed
- Initialize interrupt status baseline to undefined so first input advances
  sequence counter
- Bump reattach generation only after confirming current attempt owns the stream,
  preventing superseded results from canceling in-flight prepaint

* fix(test): increase poll iterations to prevent Node 26 test leakage

Increase event loop turns from 40 to 200 in the timer settlement loop.
Node 26's libuv poll phase can briefly starve when concurrent workers
transform tests, causing cleanup to leak into the next test. The higher
iteration count ensures async operations complete before returning.

* fix(foreground-output-budgets): use >= for budget window boundary check

At the exact window boundary, the budget should roll over. Change the
comparison from > to >= so the window resets when now equals
windowStart + FOREGROUND_BUDGET_WINDOW_MS, not just after. Add tests
to verify budget rejection and rollover behavior.

* refactor(pty-connection): add status observations and routing improvemen

- Track agent status observations with origin and transition metadata
- Separate interactive redraw input timing from general terminal input
- Restore pane authority on bind and reattach
- Refine routing trust and confirmation state handling
- Invoke queued startup callbacks when PTY is bound
- Resolve Windows shell overrides with user settings

* refactor: extract resolveLaunchAgentCandidate helper

Consolidate duplicated launch-agent resolution logic into a shared helper to prevent future divergence between paneExpectsLaunchAgent and resolveExpectedLaunchTuiAgent.

* refactor(pty-connection): use model snapshot for direct SSH reconnects

Direct SSH reconnects now restore from the full SSH model snapshot (complete scrollback) when dimensions are compatible, instead of the bounded relay tail. Falls back gracefully when incompatible or alternate-screen was exited.

* refactor(pty): retry unverifiable SSH reattaches via preserved bindings

Preserve deferred SSH session IDs longer when they serve as the only retry binding,
allowing the system to attempt recovery through direct SSH retries or PTY remounts
when reattach fails in an unverifiable way. Simplify reconnect model restoration
by removing the conditional model snapshot probe and using relay replay directly.

* test: poll terminal readiness in expectSingleOwningPty

Retry the terminal list assertion with polling to account for timing
delays in PTY state reporting from the runtime.
2026-08-23 13:55:20 -07:00
Isaque Coelhoandm4air 853afdf80e fix(status-bar): remove pet menu reserved space (#13067)
* fix(status-bar): remove pet menu reserved space

* test(status-bar): add pet segment layout validation tests

- Unit test guards against pr-[6.5rem] padding reintroduction
- E2E test measures trailing overhang instead of total width delta
  for more accurate layout validation
- Extract enableExperimentalPet helper for test clarity

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-08-23 12:21:15 -07:00
Jinjing e50cc309c3 fix(runtime): prevent restored workers from appearing idle while busy (#15998)
* fix(runtime): classify tui-idle from the visible screen only

The adopted-PTY tui-idle probe added in #15569 read the provider snapshot as
`scrollbackAnsi + data`, and the Codex readiness classifier matches the startup
banner. For a daemon-hosted adopted worker — where the retained tail stays empty
forever — every wait re-probed and could resolve `satisfied: true` off banner
history while Codex was actively working, turning a loud timeout into a silent
false ready.

- probe now requests and parses the visible grid, never scrollback
- retirement of a timed-out provider acquisition is checked before the
  re-acquire branch, so a wider row request can no longer resurrect a hung
  provider
- probe builds its result before clearing the poll interval, so a stale handle
  cannot leave the waiter with neither poll nor probe

Fixture follow-ups from the same review:
- resume legs pin the captured `launchConfig.agentCommand` to the fake instead
  of bare `codex`, which resolved the machine's real Codex off PATH
- the command override is quoted for the Windows shell the runtime will actually
  use, and specs pin that shell alongside the override
- fake agents acknowledge a bare submit after a short grace, so an unbracketed
  delivery path fails with a diagnosable ACK instead of a suite timeout

Refs STA-4907, STA-4885

* test: assert tui-idle probes serialize visible grid only

- Verify idle timeout probes exclude scrollback from serialization
- Add test case for Git Bash shell path quoting with apostrophes
- Simplify verbose test helper comments

* test: improve fake agent paste protocol validation

Refactor paste end detection to properly track both begin and end markers,
validate bracketed paste protocol (RFC 2544) through chronological event
sequencing, and emit correct error messages for protocol violations. This
ensures reliable detection of when pastes complete even when delivered
across multiple chunks, and correctly distinguishes between bracketed and
unbracketed paste modes.

* fix(runtime): reject provider snapshots when live output advances

Provider snapshots become stale when live output is received after the
snapshot is requested. Reject snapshots where the current output sequence
exceeds the snapshot sequence, preventing callers from consuming outdated
terminal state. Add tests verifying stale frame rejection.
2026-08-23 09:45:52 -07:00
Neil dee48498b2 feat(dictation): add sound-reactive grape visualizer (#16017)
* feat(dictation): add sound-reactive grape visualizer

* perf: scope dictation meter updates
2026-08-22 21:40:47 -07:00
Neil 6785dc092d fix(composer): close the Create Workspace dialog on the first Escape (#16027)
* fix(composer): close the Create Workspace dialog on the first Escape

The modal copied the page-level "Esc blurs the focused field, then closes"
rule from TaskPage/Automations. On a page that rule protects a focus the
user chose; this dialog auto-focuses the name input on open, so its
capture-phase handler preventDefault'd every first Escape (which also
suppressed Radix's dismissal, since DismissableLayer skips a
defaultPrevented event) and the dialog could only be closed with two
presses.

Drop the Escape branch and let the dialog's dismissable layer own it.
Radix dismisses only the topmost layer, so nested popovers, selects and
dialogs still consume their own Escape first.

* test(e2e): pin the composer's auto-focus as the reason one Escape must close it
2026-08-22 21:33:54 -07:00
Jinjing 113f55c5f2 test(e2e): extract paired client window reveal into helper (#15991)
* test(e2e): extract paired client window reveal into helper

Paired clients launch hidden, parking runtime subscriptions. Playwright-driven
clients must be revealed to test actual user interactions. Extract the reveal
logic into a reusable helper with error handling and unit tests.

* test(e2e): handle crash dialogs and isolate collision fixture IDs

- Recover from recoverable UI error dialogs in selectRuntimeHost
- Give the same-ID collision fixture unique repo and worktree IDs to avoid
  reusing the runtime repo's ID, preventing fixture leakage
- Simplify verbose comments for clarity
2026-08-22 16:08:21 -07:00
Jinjing 6c1286b592 Add Artifacts and Skills pages to navigation history (#15969)
* Add Artifacts and Skills pages to navigation history

- Record Artifacts and Skills visits in back/forward navigation like Automations
- Both pages properly rewind history when closed to the previous live entry
- Extract rewindHistoryIndexPastView() helper to deduplicate close-page logic across all page types
- Add test coverage for Artifacts/Skills navigation, separate entries, and shared link handling

* Add Artifacts and Skills pages to navigation history

Back/forward buttons now appear when navigating to Artifacts and
Skills pages, consistent with Terminal, Tasks, and Automations.
2026-08-22 11:00:01 -07:00
Jinjing 9ea1d28970 Fix Cmd+J Enter for worktree creation (#15970)
* fix(cmd-j): allow Enter to create worktree

* test: verify create dialog closes on Escape
2026-08-22 10:55:05 -07:00
Jinwoo Hong 1354ff534f fix(cmd-j): host-qualify browser and simulator tab candidates (STA-4965) (#15686) 2026-08-21 23:17:54 -07:00
Jinwoo Hong da6b9d8065 fix(terminal): stop orphaning live agent terminals across host restarts and graph syncs (#15644) 2026-08-21 17:11:17 -07:00
Jinwoo Hong 012e9f410c fix(runtime): recover adopted tui-idle and pin worker fixtures (#15569) 2026-08-20 15:51:48 -07:00
Jinjing d8e9fa1bb9 Revert "fix(terminal): apply pane padding on all four edges (#15544)" (#15623)
This reverts commit 4b2ed5ddd4.
2026-08-20 09:57:55 -07:00
NeilandBrennan c92f394cde fix(pty): delete the reply-withholding scheduler (#15578)
* fix(pty): answer a terminal colour query in its own turn

Root-cause follow-up to #15559, which stopped a CPR overtaking a deferred
colour reply but left the deferral itself in place.

Orca answers terminal queries by writing to the PTY master, which a line
discipline in ECHO copies straight back out as junk on a cooked prompt
(#12112). The guard was to withhold the write until an `stty` subprocess
proved ECHO clear — and forking is what forced the decision to be async.
Any deferral, however short, lets a reply written later in the same turn
overtake this one, so the async probe was the bug's root cause.

Read the bit synchronously instead. Linux and the BSDs redirect a
master's mode ioctls to the slave, so a `tcgetattr` on the master fd
node-pty already owns answers for the slave with no fork: measured 0.26us
against 2403us for the subprocess. With a verdict available inline, a
querying program that already cleared ECHO — every raw-mode prober,
including the colour probe behind the `gh auth login` report — is
answered in its own turn and can never be reordered.

The deferral stays for the genuinely cooked case, and the ordering
guarantee stays underneath it: hosts whose node-pty predates this patch
get no sync probe and fall back to the deferred path, which mixed
client/host versions make a live production path.

Reply routing is all-or-nothing: a payload needing neither containment
nor ordering stays on the host's own path, so a CPR answered during shell
startup cannot pass the daemon's post-ready flush gate and splice into
the buffered startup command.

Native side is fail-safe: a kernel that did not redirect would answer
from the master's own termios, whose ECHO defaults set, so the degraded
verdict is "echoing" — never a false "quiet". The JS half ships in the
pnpm patch while the binding needs a source build, so
ORCA_REQUIRE_NODE_PTY_ECHO_STATE=1 makes CI fail rather than silently
skip when it is handed an upstream prebuild.

Co-authored-by: Brennan <brennanb2025@users.noreply.github.com>

* fix(pty): keep the flush ordered under synchronous re-entry

Three defects found in external review of the reply-ordering work.

node-pty delivers onData inside the master write, so a query can be
answered while the queue is mid-flush. `flushPendingWrites` spliced the
array off before writing, so that reply saw an empty queue, took the
same-turn path, and landed ahead of entries the loop had not written yet
— reproduced as 01, 99, 02, 03. It now shifts one entry at a time so a
re-entrant reply queues behind the rest, bounded by the length at entry
so a re-entrant push cannot spin the loop.

An overflow flush can re-enter as far as teardown. `answer` did not
re-check `closed` afterwards, so it queued behind a closed delivery,
returned true, and the reply was never written and never reported.

The payload router's ownership comment overstated its guarantee. The
`any` semantics are deliberate — returning false after a constituent was
already written would have the caller re-write the whole payload and
duplicate it into the child's stdin — so the residual mixed-failure drop
is now documented rather than implied away.

* fix(pty): delete the reply-withholding scheduler

Orca answered a terminal query by withholding the write until a probe
proved the slave's ECHO bit was clear. That was the wrong mechanism, and
it is now gone: replies are written in the caller's turn and their echo
is contained on the output side, where it always was.

Withholding never removed an echo. The wait was bounded and always ended
in a write, so the output-side projections were doing the work the whole
time — including the readline rewrite, which happens with the tty already
raw and which therefore no reading of the ECHO bit can predict. What
withholding did add was an asynchronous write path, and that is what let
one reply overtake another and land in the next program's stdin (#15559),
what produced a re-entrancy inversion inside its own flush, and what four
rounds of regressions have lived in.

The last thing it covered was the verbatim echo of a `stty -echoctl` tty.
That shape is now projected directly. It starts with ESC, so it is
matched only when complete and never held as a partial: holding it would
take a bare trailing ESC from the query parser and an expired hold would
release it raw, so a query torn at its own ESC would never be answered.
Complete-match-only is what makes the shape safe to project at all.

Measured on a real pty: a cooked-mode master write is both echoed AND
delivered — ECHO copies the bytes without consuming them from the slave's
input queue, so a program arming raw mode with TCSANOW/TCSADRAIN (libuv's
setRawMode, hence every Node agent) still reads them. Only a TCSAFLUSH
switcher discards it, which it does on every terminal, none of which
gates a reply on termios state.

Deletes the pending-write queue, the async stty probe, the poll budget
and probe rate limit, the deadline-driven flush, and the answer/
answerInOrder split. Replies now leave in call order by construction.
No packaging, native or CI surface is touched.

* test(pty): restore stty-probe coverage and pin the duplicate-query retry

Archaeology on how withholding got here, and what its tests were really
protecting.

Deleting the ECHO probe took four tests with it that were not about the
probe at all: they cover createSttyProbe, which the shell-readiness
line-editor probe still uses — in-flight sharing, the per-platform stty
flag, and transient-versus-permanent failure latching. Restored against
the line-editor probe, which is now their only caller.

Also pins the property that answers the one case an immediate write
cannot serve. A program that queries while cooked and then arms raw mode
with TCSAFLUSH discards the reply with the rest of its input queue.
Nothing can prevent that from the terminal side, and no terminal tries.
What matters is that such a program re-queries after its own timeout: the
ingress declines to answer an already-answered slot but forwards the
duplicate downstream, so the renderer's emulator answers the retry, by
which point the program is raw. The retry path is the recovery, not
withholding.

* ci(pty): keep the fish real-PTY test in the shell-contracts lane only

Reverting pr.yml to main dropped the exclusion for the fish query-reply
test, which this branch keeps, so it would have run in the sharded lane
as well. Restores it to the shell-contracts include list and the shard
exclude list, and drops the parallelism expectations for the deleted
cooked-querier suite and the echo-state env guard.

---------

Co-authored-by: Brennan <brennanb2025@users.noreply.github.com>
2026-08-20 02:15:42 -07:00
Brennan Benson 4b2ed5ddd4 fix(terminal): apply pane padding on all four edges (#15544)
* fix(terminal): apply pane padding on all four edges

Move the configured inset onto xterm so the terminal fills its pane while the fit calculation accounts for both sides of each axis. Add a geometry golden that forces cell remainders and verifies dynamic padding without relying on renderer pixels.

* fix(terminal): normalize imported padding for fitting

* fix(terminal): align stored and fitted padding
2026-08-20 01:07:56 -07:00
Jinjing acbcb477a1 Auto e2e tests autofix scheduled ci 1h run 1 20260818T2143 (#15379)
* fix: update E2E tests for API changes and selector robustness

- Improve source control file locator specificity to avoid flakiness
- Fix board test to use correct worktree ID attribute
- Update removeWorktree calls to pass host ID parameter
- Simplify git status polling with timeout expectation

* fix: increase packaged-watchdog launch timeout and await git-status rows

Extract hardcoded 15s launch timeout to a 30s constant for better reliability under load. E2E test now waits for all git-status rows to render before asserting absence of status messages, preventing flaky passes when the list is still loading.
2026-08-19 22:39:48 -07:00
Jinwoo Hong fcdbcf85d0 fix(terminal): keep a cold-parked pane's runtime-graph leaf while its PTY lives (STA-2854) (#15514) 2026-08-19 15:47:31 -07:00
Jinwoo Hong 174039a14e fix(workspaces): re-seed a terminal when an emptied workspace is opened (#15513) 2026-08-19 14:58:00 -07:00
Neil 1ca752a7a4 test(e2e): keep the native Hangul reproduction harness (#15438)
* test(e2e): keep the native Hangul reproduction harness

This is the spec that reproduced #15299: it drives a real ibus-hangul
engine through a real compositor and asserts the bytes reaching the pty.
It is the first setup here that can exercise an input method end to end,
and three IME issues this week were unreproducible without one.

It does not run in CI, and the header says so rather than implying
coverage. It needs a compositor session CI does not have, and this repo
already carries native IME specs that are skipped everywhere and were
mistaken for protection they never gave. The run recipe is in the header
so the next person does not rebuild it.

Recorded there too are the five things that decide whether a run is real
or a silent false negative - nested rather than headless, an unused
display, a session script that does not exit, forcing the window
visible, and sending Escape before the byte reader starts. Each cost a
failed attempt, and four of them are what defeated an earlier try.

Keys and expected text are environment-tunable so other IME issues can
reuse it unchanged.

Refs #15299

* test(e2e): record three more silent-false-negative traps in the native IME harness

A Hanja candidate-selection run on the same rig hit all three. Each produced an
empty or misleading event log that reads as "the IME ignored the key" rather
than as a broken harness, which is the failure mode this header exists to
prevent.

The panel one is the least obvious: a session whose ibus-daemon runs with
--panel=disable never draws a lookup table, so any run that depends on seeing
candidates measures nothing while appearing to work.

Refs #15299
2026-08-18 23:15:32 -07:00
Jinwoo Hong 9b5538d786 fix(runtime): scope create-with-activate navigation to the requesting client (STA-2802) (#15407) 2026-08-18 21:37:35 -07:00
Jinwoo Hong a77a2f93f7 fix(remote): search Quick Open paths on the host (#15158) 2026-08-18 13:32:48 -07:00
Neil 4b2c901b66 test(terminal): pin that the CJK block is the preedit overlay, not the cursor (#15242)
* test(terminal): pin that the CJK block is the preedit overlay, not the cursor

A report described the cursor sitting on a wide character's first cell
and hiding its right half, with cursor style and opacity settings
ignored. Neither defect reproduces.

Replaying the reporter's own captured byte stream leaves the cursor at
column 11, exactly where the application asked, with correct wide and
continuation cells. A block cursor also cannot hide half a glyph: it
inverts the cell and the syllable renders inside the cursor span.

The black block is the IME preedit overlay. macOS 2-set Korean keeps the
trailing syllable composing until a terminator, so it sits in an opaque
absolutely-positioned box over the grid rather than in the buffer. That
box took stock upstream colours, black on white. It explains what no
cursor theory can: the block appears at the composing cursor cell, no
cursor option reaches it, it is identical with GPU acceleration off
since it is a DOM node above both renderers, Latin never triggers it
because Latin opens no composition, and Enter clears it because Enter
commits the composition.

Already fixed by the overlay theming in #15014, which landed a day after
the reported release, so the fix ships in the next one.

Tests only, no production change. Two pin the negative results so the
cursor explanation cannot be re-derived, and one pins the actual
mechanism at end of row, beside the existing mid-line arm.

Separately confirmed and not fixed here: the WebGL renderer drops the
cursor colour's alpha, so terminal cursor opacity genuinely does nothing
for a block cursor, which is the default style on the default renderer.
That is in the webgl addon rather than in xterm or in our code.

Refs #12729

* test(terminal): make the cursor precedence assertion real and measure the overlay

Review of the first pass found one assertion that could not fail. It set
options.cursorStyle and then read decPrivateModes.cursorStyle, which are
separate fields with separate storage, so it pinned that writing one does
not clobber the other. Deleting the precedence expression from both
renderers left it green.

It now asserts the rendered cursor class: the option style renders, a
DECSCUSR overrides it, and the reset hands control back. That fails if
the precedence is removed.

The overlay's rendered width is the one measurement in the report that
argues against our explanation, and no test here could reach it, because
the unit environment performs no layout. Adds an end-of-row browser arm
beside the existing mid-line one, asserting a single composing Hangul
syllable spans about two cells. That settles whether the block the
reporter measured at one cell can be this overlay.

Also scopes two DOM queries to the test container rather than the
document, and attaches the render listener before writing so a missed
render fails instead of hanging to timeout.

Records in the file header what it does not establish: composing the
opacity into the theme is not the same as it reaching the screen, since
the webgl renderer drops the cursor colour's alpha for a block cursor.

Refs #12729
2026-08-18 02:35:22 -07:00