mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
c3b8c145e2e060da170a300151ebd1160c045243
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
050ad3b32f |
test(e2e): normalise the pty line terminator so the IME specs can run on Windows (#13313)
* fix(terminal): show a preedit the IME resumes without a compositionstart Typing 2-Set Korean shows committed syllables but not the in-progress jamo, so the user composes each syllable blind. Long-standing hole in the vendored terminal library, not a regression: the same test fails identically against the bundle this branch starts from. The `.active` class that CSS keys `display: block` off is added only in `compositionstart` and dropped in `_finalizeComposition`. Some IMEs (observed on Windows/WSL Korean) resume a composition with a bare `compositionupdate` and no second `compositionstart`, by which point `compositionend` has already hidden the overlay, so the resumed preedit is written into a hidden element and never positioned. `updateCompositionElements` also early-returned on `!_isComposing`, so it would not lay the overlay out either. Re-show the overlay on an update that carries data, and key the layout guard on the shown overlay instead. `_isComposing` is deliberately left alone, so no commit bookkeeping changes and `onData` stays byte-identical. The two guards are equivalent on every pre-existing path: `compositionstart` sets both, `_finalizeComposition` clears both. The bundle hunks are the same two edits applied to the shipped minified output; the sourcemaps are carried through unchanged. * test(terminal): prove the resumed-preedit fix against a recorded Windows capture The synthetic test pins the shape; this replays events a real Microsoft Korean IME emitted on Windows/WSL. The capture holds three compositionupdates that resume a composition with no second compositionstart — the exact ordering that wrote the preedit into a hidden overlay. Without the fix all three report shown:false; with it all three are visible. Fixture derived from the sealed 11919-windows-wsl-current capture, which is read-only and unmodified. Co-authored-by: Orca <help@stably.ai> * test(terminal): stop the recorded Hangul fixture pinning a derivation artifact The capture logs each event twice — a dispatch record and a batched next-frame re-log. Deriving from both replayed every event twice, which made three compositionupdates appear to land after a session had ended. Filtered to dispatch records the capture holds zero resumes and 11 balanced sessions, so the previous toHaveLength(3) was pinning an artifact of the derivation. Re-scoped to what the capture does prove: the preedit stays visible across all 37 real updates. Verified by reverting the patch that this passes either way, so it is coverage and the synthetic test remains the discriminator. Both facts are now stated in the file. Co-authored-by: Orca <help@stably.ai> * fix(terminal): restore the preedit visibility patch onto its own branch The previous commit accidentally reverted it: checking main's patch and lockfile into the worktree to test whether a test discriminates also stages them, so the commit that followed swept them up. Co-authored-by: Orca <help@stably.ai> * fix(terminal): claim printable keydowns structurally so committed text survives Co-authored-by: Orca <help@stably.ai> * chore(reliability-gates): retarget the IME forwarding gate after the allowlist removal The gate listed terminal-ime-input-source.test.ts, which went with the input-source allowlist. Points at the substituted-text commit test instead, which covers what the gate is actually protecting: text committed outside a composition session reaching the pty exactly once. Co-authored-by: Orca <help@stably.ai> * docs(terminal): record why withholding a claimed keydown needs no timer The predicate withholds a keydown's byte until the commit arrives, so a key the IME eats without committing would be dropped. Measured across the recorded corpus that case does not occur, and the browser marks IME-owned presses on the keydown itself. Both facts belong next to the predicate rather than only in a handoff note, since the obvious fix for the imagined gap is a timer, and a timer here once wrote a newline the user never typed. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin the kitty all-keys-as-escape-codes hole explicitly Flag 8 asks for every printable key as an escape code; this path sends the committed text raw instead. That is a deliberate trade, not an oversight, but it was untested — the suite only covered the disambiguate flag. Pinning it makes the choice visible and records the gate to use if it ever needs closing. Co-authored-by: Orca <help@stably.ai> * fix(terminal): keep the kitty key-release report for presses that reached the pty Claiming the keyup unconditionally suppressed xterm's release report. That was sized for the old design, which claimed only a short punctuation list; the structural claim takes every printable keydown, so on macOS an app that negotiated kitty report_event_types stopped seeing releases for ordinary typing and would treat every printable key as held down. Suppress the release only when the press put nothing on the wire — swallowed by the input source, or owned by a composition transaction. xterm emits nothing from keyup unless kitty report_event_types (or win32 input mode) is on, so letting it through is inert everywhere else. Co-authored-by: Orca <help@stably.ai> * test(e2e): assert IME preedit geometry headlessly for Korean and CJK input Both IME defects that shipped and were reverted walked through a suite of ~3000 passing assertions, because every one of them was about bytes reaching the PTY. A preedit rendered into a hidden overlay satisfies all of them while the user composes blind. The real-geometry coverage that would have caught it existed but was headful, env-gated and macOS-only, so it never ran in CI. Drives composition through CDP Input.imeSetComposition instead of a native input source, which removes the accessibility grant and the system input source that forced the headful gate. The suite runs in the normal headless project in about 55s serially, and asserts the composition overlay's real bounding rect — the one property an overlay clipped to max-width:0 cannot fake and a DOM emulator cannot produce. Three tests are red on main and marked test.fail() so they stay visible in CI and flip loud when their fix lands: the preedit resumed by a bare compositionupdate, and full-width punctuation and digits committed from a keydown that still carries the ASCII layout key. Co-authored-by: Orca <help@stably.ai> * test(e2e): drop the known-broken markers now the stack closes all three Validated on real macOS hardware: with the two fixes below this layer, all three report "Expected to fail, but passed". Korean preedit renders at non-zero geometry through every jamo, and an Apple pinyin source sends ef bc 8c e3 80 82 to the pty where main sends ASCII. Worth recording why the punctuation case looked green on main once: an input source whose id happens to contain an allowlist term, as Sogou's does, satisfies the old gate. Correctness there depended on which IME the user had selected. Co-authored-by: Orca <help@stably.ai> * test(e2e): cover the Linux and Windows IME ownership shapes headlessly The ten headless IME specs on this branch all decided ownership through a macOS user-agent override, so the two platforms whose failure mode is a *dropped* character rather than a downgraded one had no coverage at all, and the one Windows-recorded trace already in the suite was replayed under whichever policy the runner happened to report — macOS locally, Linux on the CI shards. Adds four Linux specs and two Windows specs, every one of them replaying a native capture rather than a hand-authored ordering: - IBus/X11 Hangul mixed with literal ASCII. Its `compositionend` is EMPTY and the syllable arrives afterwards as a bare `insertText`, so reading the commit off `compositionend.data` — which the Windows capture rewards — drops every syllable on this framework. - fcitx5/Wayland Hangul. No keydown at all for a composing key, not even 229, and physically wrong `code` values on the literal keys. Any ownership rule reading 229 or `code` fails here. - Numeric pinyin candidate selection under both frameworks, with the ordinary digit kept as the negative control, so the two directions are pinned against each other rather than separately. - Windows Microsoft Korean captured with real scan codes, including the two lines committed with Shift held. Each asserts both sides of the boundary: the preedit's real geometry at every frame the user would see, and the exact byte stream the native run put on the PTY. The recorded `onData` the Windows/WSL fixture already carried is now asserted instead of sitting unused. Chinese moves up to first-class alongside Korean: pinyin preedit width is now pinned the way the Japanese phrase already was, and full-width punctuation is covered in the composition-session shape the Windows and Linux frameworks use, not only the macOS insertText shape. Two harness fixes fell out of the recorded traces and are why the IBus one passes. The replay applied each event's recorded textarea state *after* dispatch, one event too late for the handlers that read `textarea.value`; and it left a task boundary between `compositionend` and the `input` carrying the commit, which Chromium never inserts, letting xterm's deferred finalizer settle against a textarea the committed text had not reached yet. Co-authored-by: Orca <help@stably.ai> * test(e2e): replay the recorded macOS IME shapes instead of only synthesising them The macOS coverage on this branch drives Chromium composition through CDP, which is genuine but hand-ordered, so it could not assert the one property that decides the macOS rule: a composing keydown arrives with keyCode 229 while `key` is still the single translated character the input source produced — `ㅎ`, not `Process` — which is indistinguishable by length from an ordinary printable key. Three native captures were sitting unused in the evidence set. Adds a recorded 2-Set Korean session, including the syllable boundary where one composition closes and the next opens with no keydown between them, asserted against its own recorded byte stream. Adds the third failure mode, which had no coverage in any shape: an abandoned preedit leaking to the shell. Pinyin and Cangjie both backspace a composition away to nothing, and the assertion is not "the right bytes" but "no bytes". Both cancellation captures continue with a literal `ordinary` typed as bare keydowns, the recorder's own negative control. That tail carries no `input` events because the build it was captured on produced the byte from the keydown itself, so replaying it would measure the recorder rather than the product; the specs cut at the `compositionend` and say so. Co-authored-by: Orca <help@stably.ai> * test(e2e): promote the non-allowlist input-source punctuation spec to the suite Qingg matches none of the terms the pre-structural build enumerated, so on that build its bypass never installs. It is the one arm no headless spec can express, and the only test here that the pre-structural build cannot pass. Promoted from scratch with four changes, each forced by a measurement rather than by taste: - A non-attached input method is a refusal, not a negative result. macOS attaches per app instance and the attach can simply fail — 3 of 6 instances under exclusive host access, and re-selecting the source did not recover one of them across 9 keystrokes. That is now `test.skip()` with a reason naming the rerun, not a thrown error, and the suite does not gate on a fully green session. - Attachment is probed with a LETTER. Punctuation substitution emits no compositionstart and no keyCode 229 even under a fully attached source, so at the keydown it is indistinguishable from having no source at all. The punctuation arms are judged on PTY bytes alone. - The ASCII-layout control is now part of the spec rather than a side experiment. Without it a build that rewrote every `.` into `。` unconditionally would pass the Qingg arm and be badly wrong. - The assertion runs by default instead of behind a strict-mode flag, and gained a non-vacuity check: the input source must have committed something. That is the sharp end of the mechanism — on the old build the DOM carries only keydown and keyup, so nothing is committed at all and the character is destroyed before the source is asked. The verdict stays an equality between two measurements, never a comparison against a hardcoded glyph, so it holds whatever punctuation mode the operator's input source happens to be in. It reads `beforeinput`, not `input`: the forwarder consumes `input` in the capture phase on the pane element, so a probe on the helper textarea never sees it and a strict run fails with correct bytes underneath. Co-authored-by: Orca <help@stably.ai> * test(e2e): normalise the pty line terminator so IME specs can run on Windows A Unix pty's line discipline turns the terminal's CR into a bare LF, but Windows ConPTY hands the reading process CRLF. Every spec compares against a recorded line ending in LF, so on Windows 16 of 21 failed on the terminator alone while the IME payload bytes ahead of it were byte-for-byte correct. Verified on real Windows: the renderer-to-pty boundary assertion passed there, so the app writes a bare CR and the LF is added by the console downstream of anything we control. Normalising in the reader keeps the specs asserting the IME bytes unchanged rather than loosening them. Co-authored-by: Orca <help@stably.ai> * chore: drop non-mergeable IME e2e scratch files --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
17cfc968cf |
Revert the terminal IME composition-ownership change (#13282)
* Revert "test(ime): restore coverage the composition-ownership change removed (#13168)" This reverts commit |
||
|
|
17b3dff3c4 |
refactor(terminal): return IME composition ownership to xterm (#13128)
* fix(terminal): return IME composition ownership to xterm * fix(mobile): derive terminal input from native replacement ranges * test(mobile): record iOS Japanese IME traces * fix(mobile): preserve native IME replacement ranges * fix(xterm): flush queued application input after IME commit * test(terminal): pin Korean intermediate commit * test: pin Windows IME shortcut ownership * test: replay IBus number candidate commit * fix: preserve native macOS input-method punctuation * refactor(terminal): remove stale mac focus override * fix(mobile): preserve soft keyboard deletion ranges * fix: keep IME-owned palette chords in renderer * fix: stop carried IME shortcuts at renderer owner * fix: preserve carried IME shortcut dispatch * fix: narrow main-owned shortcut actions * test(mobile): pin Japanese IME replacement traces * test(terminal): retain paired native IME trace * fix(chat): preserve browser IME composition ownership * fix(chat): retain macOS IME confirm gesture * fix(chat): expire unmatched IME confirm carry * fix(chat): isolate IME confirmation expiry * fix(chat): retain active IME confirmation * refactor(terminal): remove dead composition handler * feat(ime): add shared Enter-ownership seams for CJK composition The confirming Enter of a CJK composition arrives as two keydowns and the orderings differ by platform: Windows/Linux redispatch the unmarked Enter/13 before keyup, macOS delivers keyup first. A guard reading only isComposing or keyCode 229 misses the redispatch, so surfaces submitted on a confirm. Adds useImeEnterGestureOwnership (carry token, next-frame expiry), a shared ImeEnterGuardedForm for native implicit submission, and the cmdk seam covering 18 CommandInput surfaces at one site. A chorded Enter arms the carry but is never swallowed — the reverse would eat a user's deliberate Cmd/Ctrl+Enter. Both failure modes are pinned by ime-enter-gesture-ownership-contract.test.ts. Co-authored-by: Orca <help@stably.ai> * refactor(terminal): consolidate native input listeners and parked-screen owner Extracts the shared native-input listener installer and renames the parked-screen detector for what it actually does, replacing per-call-site duplication. The listener installer keeps a forgetOptionKeyLocationOnBlur flag so per-window semantics are preserved rather than flattened. Net deletion; no behaviour change intended. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin recorded IME shapes as regression tests Nine regression tests built from hashed affected-platform captures, each with a paired ordinary negative and a discriminating mutation verified to take the file from all-passing to exactly one failure. Covers the Windows MS-Korean Shift family (#12179, #11878, #12151, #11946, #12152) and the Korean TUI line-break rows (STA-3237, STA-3222, STA-3129). STA-3237 pins the empirical 3-Shift / 2-active-composition / 2-newline ratio the device run established — the third Shift produces nothing because Space has already committed. That ratio is not derivable from a static capture. Co-authored-by: Orca <help@stably.ai> * fix(ime): guard Enter-commit surfaces against CJK confirm Applies the Enter-ownership guards across the surfaces whose Enter commits something: publishes, clones, pairs, installs, posts, or persists. Tiered deliberately rather than uniformly. Irreversible and remote-effect sites take the carry token, which also blocks the unmarked redispatch. Locally reversible sites take the oracle check with a one-line comment naming the residual, because a spurious commit there costs one undo. Three numeric fields are left unguarded with the reason in-code: Chromium blanks number inputs at compositionstart, so a confirm-Enter only ever reaches an empty-draft reset. Measured with a CDP probe rather than assumed — a guard that cannot fire is noise. Co-authored-by: Orca <help@stably.ai> * test(ime): teeth-check the Enter guards on every guarded surface One suite per guarded surface, each verified by deleting the guard and confirming the test fails. A green guard test without that check is unverified, not verified. Two shapes pass vacuously in happy-dom and are avoided here: native implicit form submission never fires, and blur() is inert on an unfocused element. Both made "the commit did not happen" assertions pass with the guard removed, so the suites assert the guard's contract directly instead. Co-authored-by: Orca <help@stably.ai> * fix(mobile): keep iOS Korean commits whole through the live-input path iOS Korean reports isComposing: false on every event, so it bypasses the composition guard entirely. The strict owner rejected UIKit's transformed post-change field and sent only the leading jamo — the reported symptom. Prefers the authoritative same-event field text over the predicted text when the supplied operation cannot produce it. Generic: no Korean special-case, no locale classifier, no normalization. Adds the RN-target-keyed submit carry alongside it. Co-authored-by: Orca <help@stably.ai> * test(e2e): make IME capture harnesses fail loudly instead of silently Four instruments recorded silence as success, so a void run scored as a clean one: - readTerminalImeBoundaryTrace returned an empty trace when the probe never installed, making every "nothing leaked" negative pass vacuously - summarizeLatencies([]) returned a perfect zero distribution that passed all three latency thresholds - the macOS Vietnamese spec pinned an input-source ID that does not exist, and failed as though the operator had chosen the wrong source - the expectedLineCount=1 prefix property was undocumented and one edit from silently downgrading a PTY assertion Input sources now resolve by enumeration and name the near-matches on failure. Co-authored-by: Orca <help@stably.ai> * test(terminal): cover Cangjie cancellation and fix a cross-namespace assertion Adds #11951's recorded Cangjie cancel shape to the existing cancellation suite, which covered Pinyin and Sogou but not Cangjie. One keystroke then Backspace arriving as deleteContentBackward with data: null, so the stale preedit is the only thing a fallback could replay. Verified against the historical pre-6cd944c62b3 bundle: the positive fails with ['尸'] where [] is expected, while the ordinary negative stays green. Also fixes the Vietnamese spec, which asserted a TIS-space input-source ID against getKeyboardInputSourceId(). Those two Orca APIs report the same source in different namespaces — TIS nests it under VietnameseIM, the app API does not. The resolver stays as an installation precondition; the assertion matches the leaf. Co-authored-by: Orca <help@stably.ai> * test(e2e): add a real-IME macOS arm for the Korean chord commit The existing korean-ime-terminal-shift-enter-commit spec synthesizes composition over CDP: Input.imeSetComposition sets the preedit directly and Input.insertText performs the commit. Asserting the IME produced events you injected yourself is circular, so that spec cannot certify real-IME behaviour. This arm selects 2-Set Korean via TIS, reads it back live, and injects through System Events key codes, so the OS owns the preedit, the commit instant, and isComposing. PTY byte expectations are preserved verbatim. Covers 2 of the original 4 cases by design. The other two are the Windows/Linux redispatch-before-keyup ordering, which macOS cannot produce and which cannot be selected -- the OS decides it. Reintroducing synthesis to "restore coverage" would reintroduce the circularity. Co-authored-by: Orca <help@stably.ai> * test(e2e): assert the macOS chord arm at the PTY boundary, not the renderer The byte expectations were transcribed from korean-ime-terminal-shift-enter-commit :364/:383, which assert against onData -- a renderer boundary where the terminator is CR. This spec reads the PTY child, where the tty has already converted CR to LF. Names both forms per row rather than swapping the constant, so the conversion reads as evidence that the capture reached past the renderer, as #11936 and #11951 record. Ctrl+Enter's CSI-u sequence is unaffected and is identical at both boundaries. Co-authored-by: Orca <help@stably.ai> * test(e2e): measure composer-to-onData latency and stop dropping IME keystrokes Two defects in the echo latency probe. It hooked onWriteParsed and onRender but never onData, so it measured key->parse->render echo rather than the composer-vs-onData delta the latency rows need. Adds a third hook feeding its own sample set. And `event.key.length !== 1` silently dropped IME keystrokes: Pinyin and Cangjie keydowns arrive as key:'Process' (length 7). Replayed over the captured corpus, the old filter accepted 580 of 4137 Chinese IME keydowns -- it was discarding 80% of them. The new filter matches the shape the owner itself branches on. Attribution charges each onData to the latest keydown rather than a FIFO head, because composing jamo emit no onData at all and a queue would credit a whole composition to its first keystroke. The consumer now asserts sample count before any percentile, so a zero-sample run cannot render as a flawless distribution. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin the WSL shifted-jamo newline shape for #11919 In Korean 2-set, Shift types ordinary letters -- the double consonants and the compound vowels. Each such keystroke reaches Chromium as key='Process', keyCode=229, shiftKey=true. The v1.4.163 classifier matched exactly that pattern with no code guard, so it called those keystrokes Enter, rewrote them to a synthetic Shift+Enter, and injected a newline into the middle of the word -- with no Enter key pressed. That is why the reporters said "no modifier key pressed": they had not chorded Shift+Enter, but they had pressed Shift, to type the double consonant. Asserts the row's own recorded capture: 40 immediate keydowns, exactly 3 of them Shift-carrying inside a single syllable, and an onData stream with one newline per Enter press and none mid-word. Two ordinary negatives keep it from being a blanket mute -- the same session's non-IME keydowns still reach shortcut policy, and an ordinary Shift+Enter still resolves through the real policy. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin the composition commit lag that made Korean type one behind macOS Korean 2-Set commits syllable N only when the first jamo of N+1 arrives, so compositionend and compositionstart land in the same task. A composition-start handler cancelled the pending finalizer that was the only path to triggerDataEvent and ended the session without emitting bytes, so every committed syllable reached onData exactly one syllable late and the backlog cleared only at a Space or Enter. Types continuously with no Enter and no Space -- either would flush the backlog and hide it -- and samples onData at every syllable boundary. Paired with a length-matched ASCII arm that stays green throughout, so the positive is a fact about composition rather than about timing in general. Bisected to a single call site across five builds: pristine, 1.4.155 and 1.4.162 pass, 1.4.163 fails, removing the one call repairs it, restoring it fails identically. That window is exactly the reporter's "started immediately after updating". Co-authored-by: Orca <help@stably.ai> * test(mobile): cover the send-queue abort that silently drops queued keystrokes One failed send in use-terminal-live-input-commit aborts every keystroke queued behind it, with the error swallowed by .catch(() => false). The existing test resolves(true) on every send, so the failure branch was uncovered. Four arms: the abort itself, an ordinary negative on the healthy path, a throwing sender, and a liveness control proving the queue recovers once the chain settles. Deleting the abort takes 4 passed to 3 failed, with the ordinary negative correctly surviving. Scope is stated in the docblock: this is a transport send-queue abort, reachable only via a real disconnect or RPC error. REQUEST_TIMEOUT_MS is 30s, so latency alone cannot reach the branch — consistent with #7094's symptom class, not proven to be its cause. * test(terminal): pin that daemon snapshot/restore cannot disturb a composition Two independent reporters attributed broken Korean composition to the always-on PTY daemon repainting terminal state over the preedit. The attribution is wrong on ancestry — the daemon shipped three months before the version both call good — but the boundary was never actually tested. Runs the real applyMainBufferSnapshot choreography against a live composition, including the full 2J/3J/H wipe plus the resize and alt-screen branches. textarea.value, selectionStart/End, compositionView.textContent and .active all survive byte-identical, and interleaving a restore between every jamo of 문제 still commits 문제 at onData. Also pins that the uncommitted preedit is absent from the captured snapshot: it lives in the textarea, never the buffer, so a restore has nothing stale to echo back. Injecting one textarea.value = '' into the restore fails exactly the three restore-boundary tests. * test(terminal): pin that Cmd tears down a composition where Ctrl and Shift do not xterm's composition keydown exempts only keyCode 16/17/18 (Shift/Ctrl/Alt) plus 20/229. macOS Meta — 91/93/224 — is absent, so a Cmd press mid-composition takes _finalizeComposition(false): the overlay goes dark and never recovers, because compositionstart is not re-fired. The user composes the rest of the word blind. Linux and Windows users press Ctrl and are exempt. xterm already has a Meta-aware modifier predicate in wasModifierKeyOnlyEvent, so this is an internal inconsistency rather than a deliberate choice. Owns no reported row and is version-neutral: 5/5 on both 1.4.162 and 1.4.163. The branch is unexercised in all 328 recorded traces, so this is a hazard pin, not a regression guard. Only the teardown is asserted; the likely duplicated commit needs a compositionend the IME kept alive across the Cmd, which no capture contains. Deleting the exemption fails exactly the three paired negatives; adding Meta to it fails exactly the two Cmd arms. * test(native-chat): characterize preedit loss when a question card replaces the composer An AskUserQuestion card fully replaces the composer by design, but the in-flight composition goes with it: the composer unmounts before compositionend reaches it, so the preedit is never committed to the draft. The committed text survives only because the draft is cached and restored via defaultValue. Node identity changes, value 'abc' is preserved, the 가 is gone. Drives the real NativeChatView -> SessionGate -> InteractiveCard -> questionActive swap -> Composer -> ComposerField, flipped by writing the same store field an AskUserQuestion hook event writes. Flipping questionActive to false fails exactly this test and nothing else across 639 native-chat tests, so the path was entirely unguarded. CHARACTERIZATION TEST: it asserts the loss. Fixing the defect — committing the preedit before the swap, or keeping the composer mounted — will make this file fail. Update the expectations to the new contract rather than working around them. Owns no reported row. #12118/STA-3219 flicker is keyed to token counters, which provably do not remount, and a question card arrives once per question. * test(terminal): pin the duplicated commit when Meta interrupts a composition _finalizeComposition(false) sends textarea.value.substring(start, end) but cannot clear the IME-owned textarea, so a later compositionend re-sends the same range. Meta reaches that path because CompositionHelper exempts only Shift/Ctrl/Alt; xterm's own wasModifierKeyOnlyEvent covers Meta four ways, so the omission is an internal inconsistency rather than a choice. Companion to the modifier-exemption guard, which deliberately pins only the overlay teardown. This pins the data consequence. HAZARD PIN: owns no reported row. The trigger is unverified on hardware — no capture in the corpus contains a Meta-during-composition gesture, and whether macOS keeps the composition alive across it is unmeasured. The duplication follows from the code given that sequence; whether users reach the sequence is the open half. An earlier premise that Space (keyCode 32) reaches this path was refuted by a corpus scan: 0 of 731 evidence files carry a keyCode-32 Space while composing, against 171 at 229, and 229 returns early. * test(terminal): characterize the syllable lost when the textarea blurs mid-composition CoreBrowserTerminal._handleTextAreaBlur clears the helper textarea unconditionally — "Text can safely be removed on blur" — while CompositionHelper._finalizeComposition reads the committed text back out of that same value from a deferred timeout. By the time it runs the value is empty, the substring is '', and triggerDataEvent never sees the syllable. xterm checks composition state in _syncTextArea and omits the same check here. Six cases. Blurring mid-composition loses the syllable in every ordering, including compositionend-before-blur, which is Chromium's real order — so it is not an ordering artifact. A bare textarea.blur() with no Orca code loses it too, which places the owner upstream: Orca's unguarded release on outside pointerdown is one trigger, not the cause. Committing 한 then blurring mid-가 yields ['한'] where ['한','가'] is correct: one syllable gone, surrounding text intact. Teeth checked by inverting — adding an Orca-side composition guard flips exactly the three cases that route through the release path and leaves the bare-blur and no-blur cases green, which is the scope split: a fix in regular-terminal-focus-ownership alone would not close this. HAZARD PIN, but unlike the others this one has a real production injector — clicking outside the terminal mid-composition. Owns no reported row. The shape matches #9738's report; the injector does not, and a shape match with a mismatched injector is not an owner. * test(terminal): say which arm the STA-3237 fixture came from The recorded keydowns are wave 4's A-shift-unmarked-only — the arm that emits no PTY bytes. Nothing in the file said so, so two readers concluded the row's events fail the owner's predicate and that STA-3237 and STA-3222 were different defects. They share an owner; the arm that fires is Process/229+Shift, absent from this bubble-phase trace because the owner claims it in the capture phase. Also corrects "code-blind": the v1.4.163 policy emits \x1b\r only for a shift-only key:'Enter', and a jamo keydown reaches that branch solely via the isTerminalImeProcessEnter rewrite. The mock is deliberately wider so the ownership guard stays under test if that rewrite moves. Comments only — no assertion, fixture value, or mock behaviour changed. * test(e2e): track the input-source selector the macOS specs shell out to Five tracked macOS IME specs ran `swift .tmp/select-input-source.swift`, a file that is gitignored and existed only on one machine. Anyone else checking out the repo — or the same machine after .tmp is cleaned — could not run them, and they are the capture drivers for the macOS rows that are blocked waiting for exactly those runs. Moves it to tests/e2e/ beside its callers. The chord spec now resolves it from __dirname rather than reaching two levels up into .tmp. * test(terminal): pin the CJK repaint decision against the reporter's own output #12164 comment 1 and #5921 report agent output with double-width glyphs rendering duplicated character-by-character while ASCII in the same line stays clean. No IME, no composition, no keystroke — the user never types the CJK. Segmenting all three verbatim samples into maximal same-risk-class runs gives 33 runs and zero violations of "this run is corrupted iff the production detector flags it": 17 wide runs all corrupted, 16 narrow runs all byte-identical. The paired negative is co-located in the same line rather than in a separate run — the reporter supplied it without knowing. Doubling is asserted as present, not uniform: 자바스크립트 and 시스템 each leave a jamo undoubled, which is a repaint-region boundary artifact rather than a per-character transform. The discriminating arm is in the test rather than a source mutation: |
||
|
|
fe6f929c6e |
fix(terminal): reconcile cross-platform IME composition lifecycle (#11293)
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: JeongUk Park <jeongph.dev@gmail.com> |