mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
fix(xterm): stop swallowing a keystroke typed after an IME commit
Type a Japanese segment, convert it, then press a key one macrotask later and that key was lost. No modifier, nothing exotic — every user who keeps typing straight after converting. Korean surfaced it first only because 2-Set composes on nearly every keystroke. handleCompositionInput discarded the payload unconditionally in the window after the deferred send: _isSendingComposition stays true for one macrotask after the timer cleared _pendingCompositionStart, and the branch substituted '' for whatever arrived. The suppression is not itself wrong — it de-duplicates IMEs that deliver their commit an event-loop turn late, which terminal-stock-composition.test.ts pins. It just could not tell a duplicate from new input, because the two events are identical apart from payload. Now it compares against _sentComposition, the text the deferred send actually emitted, and discards only a match. A flag-timing redesign was measured first and rejected: it fixed this and duplicated on IBus, because no timing change can separate events that differ only in content. Edited in config/patches/xterm-src/ and regenerated through the harness, so the emitted patch and the lockfile hash are derived, not hand-written. The commit-overlap pin's swallow arm now asserts the repaired contract — the value its own comment already named as correct and as what stock beta.287 emits. #11504's arm at :184 flips too; it never covered that report, as its own prior note recorded, and the reporter's +149ms arm is untouched and still asserting the defect. Provenance hashes in three test docblocks are updated, since regenerating changes the patch hash and with it the resolved install directory.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -48,7 +48,7 @@ index 497afcf535f3eaca00889525a77e15eb633ccd96..e3cad77734795f6cf34bb7120264fe81
|
||||
compositionupdate(ev: CompositionEvent): void;
|
||||
compositionend(): void;
|
||||
diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts
|
||||
index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155eeabe838 100644
|
||||
index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d5c532339da18e0fecf6193ad579839bae269b83 100644
|
||||
--- a/src/browser/input/CompositionHelper.ts
|
||||
+++ b/src/browser/input/CompositionHelper.ts
|
||||
@@ -5,7 +5,6 @@
|
||||
@@ -59,7 +59,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
|
||||
interface IPosition {
|
||||
start: number;
|
||||
@@ -42,15 +41,8 @@ export class CompositionHelper {
|
||||
@@ -42,15 +41,9 @@ export class CompositionHelper {
|
||||
*/
|
||||
private _isSendingComposition: boolean;
|
||||
|
||||
@@ -74,10 +74,11 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
- private _textareaChangeTimer?: number;
|
||||
+ private _pendingCompositionStart?: number;
|
||||
+ private _pendingInput = '';
|
||||
+ private _sentComposition = '';
|
||||
|
||||
constructor(
|
||||
private readonly _textarea: HTMLTextAreaElement,
|
||||
@@ -64,7 +56,6 @@ export class CompositionHelper {
|
||||
@@ -64,7 +57,6 @@ export class CompositionHelper {
|
||||
this._isSendingComposition = false;
|
||||
this._compositionPosition = { start: 0, end: 0 };
|
||||
this._compositionSuffix = '';
|
||||
@@ -85,7 +86,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,10 +71,24 @@ export class CompositionHelper {
|
||||
@@ -80,10 +72,27 @@ export class CompositionHelper {
|
||||
this._compositionPosition.end = Math.max(start, end);
|
||||
this._compositionSuffix = this._textarea.value.substring(this._compositionPosition.end);
|
||||
this._compositionView.textContent = '';
|
||||
@@ -96,7 +97,10 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
+ public handleCompositionInput(data: string, nativeCommit: boolean): boolean {
|
||||
+ if (nativeCommit) {
|
||||
+ if (!this._isSendingComposition) return false;
|
||||
+ const input = (this._pendingCompositionStart === undefined ? '' : data) + this._pendingInput;
|
||||
+ // Once the deferred send has run, an IME that delivers its commit a task late (IBus) repeats
|
||||
+ // text we already sent; anything else is new input and must not be discarded with it.
|
||||
+ const alreadySent = this._pendingCompositionStart === undefined && data === this._sentComposition;
|
||||
+ const input = (alreadySent ? '' : data) + this._pendingInput;
|
||||
+ this._pendingCompositionStart = undefined;
|
||||
+ this._pendingInput = '';
|
||||
+ if (input.length > 0) this._coreService.triggerDataEvent(input, true);
|
||||
@@ -111,7 +115,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
/**
|
||||
* Handles the compositionupdate event, updating the composition view.
|
||||
* @param ev The event.
|
||||
@@ -129,9 +134,6 @@ export class CompositionHelper {
|
||||
@@ -129,9 +138,6 @@ export class CompositionHelper {
|
||||
}
|
||||
|
||||
if (ev.keyCode === 229) {
|
||||
@@ -121,7 +125,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -153,7 +155,11 @@ export class CompositionHelper {
|
||||
@@ -153,7 +159,11 @@ export class CompositionHelper {
|
||||
if (!waitForPropagation) {
|
||||
// Cancel any delayed composition send requests and send the input immediately.
|
||||
this._isSendingComposition = false;
|
||||
@@ -134,7 +138,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
this._coreService.triggerDataEvent(input, true);
|
||||
} else {
|
||||
// Make a deep copy of the composition position here as a new compositionstart event may
|
||||
@@ -163,6 +169,7 @@ export class CompositionHelper {
|
||||
@@ -163,6 +173,7 @@ export class CompositionHelper {
|
||||
end: this._compositionPosition.end
|
||||
};
|
||||
const currentCompositionSuffix = this._compositionSuffix;
|
||||
@@ -142,7 +146,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
|
||||
// Since composition* events happen before the changes take place in the textarea on most
|
||||
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
|
||||
@@ -175,12 +182,10 @@ export class CompositionHelper {
|
||||
@@ -175,12 +186,10 @@ export class CompositionHelper {
|
||||
this._isSendingComposition = true;
|
||||
setTimeout(() => {
|
||||
// Ensure that the input has not already been sent
|
||||
@@ -158,13 +162,14 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..912430b4e977cb61feacdaa39ea84155
|
||||
if (this._isComposing) {
|
||||
// Use the start position of the new composition to get the string
|
||||
// if a new composition has started.
|
||||
@@ -195,47 +200,21 @@ export class CompositionHelper {
|
||||
@@ -195,47 +204,22 @@ export class CompositionHelper {
|
||||
: value.length;
|
||||
input = value.substring(currentCompositionPosition.start, Math.max(currentCompositionPosition.start, valueEnd));
|
||||
}
|
||||
- if (input.length > 0) {
|
||||
- this._coreService.triggerDataEvent(input, true);
|
||||
- }
|
||||
+ this._sentComposition = input;
|
||||
+ input += this._pendingInput;
|
||||
+ this._pendingInput = '';
|
||||
+ if (input.length > 0) this._coreService.triggerDataEvent(input, true);
|
||||
|
||||
Generated
+24
-24
@@ -19,7 +19,7 @@ patchedDependencies:
|
||||
hash: 6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258
|
||||
path: config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch
|
||||
'@xterm/xterm@6.1.0-beta.287':
|
||||
hash: f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e
|
||||
hash: 8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d
|
||||
path: config/patches/@xterm__xterm@6.1.0-beta.287.patch
|
||||
node-pty@1.1.0:
|
||||
hash: 8fc49f17011b6611a5b8c00e83a6f12e14e75aada2b0ef26dc5393f8376d20e8
|
||||
@@ -46,7 +46,7 @@ importers:
|
||||
version: 2.5.6
|
||||
'@xterm/addon-serialize':
|
||||
specifier: 0.15.0-beta.287
|
||||
version: 0.15.0-beta.287(patch_hash=96f70e83261df6a29ad7590feb08b988670655ced7596e77253d524f73f608dd)(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))
|
||||
version: 0.15.0-beta.287(patch_hash=96f70e83261df6a29ad7590feb08b988670655ced7596e77253d524f73f608dd)(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))
|
||||
'@xterm/headless':
|
||||
specifier: 6.1.0-beta.287
|
||||
version: 6.1.0-beta.287
|
||||
@@ -206,25 +206,25 @@ importers:
|
||||
version: 5.2.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))
|
||||
'@xterm/addon-fit':
|
||||
specifier: 0.12.0-beta.287
|
||||
version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))
|
||||
version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))
|
||||
'@xterm/addon-ligatures':
|
||||
specifier: 0.11.0-beta.287
|
||||
version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))
|
||||
version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))
|
||||
'@xterm/addon-search':
|
||||
specifier: 0.17.0-beta.287
|
||||
version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))
|
||||
version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))
|
||||
'@xterm/addon-unicode11':
|
||||
specifier: 0.10.0-beta.287
|
||||
version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))
|
||||
version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))
|
||||
'@xterm/addon-web-links':
|
||||
specifier: 0.13.0-beta.287
|
||||
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))
|
||||
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))
|
||||
'@xterm/addon-webgl':
|
||||
specifier: 0.20.0-beta.286
|
||||
version: 0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))
|
||||
version: 0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))
|
||||
'@xterm/xterm':
|
||||
specifier: 6.1.0-beta.287
|
||||
version: 6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)
|
||||
version: 6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@@ -9582,39 +9582,39 @@ snapshots:
|
||||
|
||||
'@xmldom/xmldom@0.8.13': {}
|
||||
|
||||
'@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))':
|
||||
'@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)
|
||||
|
||||
'@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))':
|
||||
'@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)
|
||||
lru-cache: 11.5.1
|
||||
opentype.js: 2.0.0
|
||||
|
||||
'@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))':
|
||||
'@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)
|
||||
|
||||
'@xterm/addon-serialize@0.15.0-beta.287(patch_hash=96f70e83261df6a29ad7590feb08b988670655ced7596e77253d524f73f608dd)(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))':
|
||||
'@xterm/addon-serialize@0.15.0-beta.287(patch_hash=96f70e83261df6a29ad7590feb08b988670655ced7596e77253d524f73f608dd)(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)
|
||||
|
||||
'@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))':
|
||||
'@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)
|
||||
|
||||
'@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))':
|
||||
'@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)
|
||||
|
||||
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e))':
|
||||
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)
|
||||
|
||||
'@xterm/headless@6.1.0-beta.287': {}
|
||||
|
||||
'@xterm/xterm@6.1.0-beta.287(patch_hash=f4db57f4b724063e5888942227a36be616e72f05bc9fde69204d4689ae253f3e)': {}
|
||||
'@xterm/xterm@6.1.0-beta.287(patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d)': {}
|
||||
|
||||
abbrev@4.0.0: {}
|
||||
|
||||
|
||||
+3
-1
@@ -45,7 +45,9 @@
|
||||
// Measured, not inferred: every expectation was read off onData against the @xterm/xterm
|
||||
// this repo installs and ships.
|
||||
// src/browser/input/CompositionHelper.ts
|
||||
// sha256 10893b3e609b3a1d296e03be03e397b5044308d7f7b6a3da6efd06a545c13a21
|
||||
// sha256 d6393a7e805139c1b8791a8996a42b7683e7bb0e03c137e98a021917605c1635
|
||||
// (patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d;
|
||||
// re-measured after the composition-commit dedup fix, expectations unchanged)
|
||||
// src/browser/CoreBrowserTerminal.ts
|
||||
// sha256 06efd181ba938e7d3be8c49eef2e6335c9e32fc7e41ac47102a32fc4e32d8678
|
||||
// regular-terminal-focus-ownership.ts (the Orca call site under test)
|
||||
|
||||
+32
-24
@@ -16,29 +16,33 @@
|
||||
// Cmd was checked against the swallow in (2) and does NOT reach it: Cmd duplicates,
|
||||
// it does not drop. The two hazards below share no trigger.
|
||||
//
|
||||
// 2. An uncomposed insertText landing in the window after the commit timer has already
|
||||
// sent is swallowed whole. _isSendingComposition stays true for one macrotask after
|
||||
// the timer cleared _pendingCompositionStart; handleCompositionInput passes the first
|
||||
// check, then reads the cleared sentinel and substitutes '' for the data.
|
||||
// 2. FIXED — the arm below now asserts the repaired contract, not a defect. An uncomposed
|
||||
// insertText landing in the window after the commit timer had already sent used to be
|
||||
// swallowed whole: _isSendingComposition stays true for one macrotask after the timer
|
||||
// cleared _pendingCompositionStart, and handleCompositionInput passed the first check,
|
||||
// then read the cleared sentinel and substituted '' for the data — unconditionally, so
|
||||
// it ate genuinely new input along with the duplicate it was aimed at.
|
||||
// handleCompositionInput now compares the payload against _sentComposition, the text
|
||||
// the deferred send actually emitted, and discards only a match.
|
||||
//
|
||||
// THIS NAMES THE TRIGGER (2) PREVIOUSLY LACKED, and it is an ordinary one. Hazard (1)
|
||||
// is Cmd's; this one was left with no trigger at all, which reads as exotic. It is
|
||||
// THIS NAMED THE TRIGGER (2) PREVIOUSLY LACKED, and it is an ordinary one. Hazard (1)
|
||||
// is Cmd's; this one was left with no trigger at all, which reads as exotic. It was
|
||||
// not. A differential run through Japanese
|
||||
// multi-segment conversion found shipped behaviour swallows an ordinary Latin key
|
||||
// multi-segment conversion found the shipped patch swallowed an ordinary Latin key
|
||||
// typed one macrotask after a conversion commit: type a segment, convert, then press
|
||||
// `a`, and the `a` is lost. No modifier, no exotic gesture — every Japanese user who
|
||||
// `a`, and the `a` was lost. No modifier, no exotic gesture — every Japanese user who
|
||||
// keeps typing straight after converting. Korean surfaced it first only because
|
||||
// 2-Set composes on nearly every keystroke.
|
||||
//
|
||||
// The suppression is NOT a defect on its own: it de-duplicates IMEs that deliver
|
||||
// their commit insertText a task after compositionend (IBus, Mozc), which
|
||||
// terminal-stock-composition.test.ts pins. This swallow is that dedup's false
|
||||
// positive. The two events are structurally identical — same inputType, same
|
||||
// composed, same preceding 229 keydown — and differ only in payload, so no
|
||||
// flag-timing change separates them. A redesign was built and measured: it fixes
|
||||
// this and duplicates on IBus. A content-aware variant fixes both at +5 lines but
|
||||
// flips a reported row's test, and is blocked regardless while the patch cannot be
|
||||
// regenerated. See .tmp/ime-handoff/swarm-scratch/lane-group-e-redesign/.
|
||||
// terminal-stock-composition.test.ts pins and which the fix preserves. The swallow was
|
||||
// that dedup's false positive. The two events are structurally identical — same
|
||||
// inputType, same composed, same preceding 229 keydown — and differ only in payload,
|
||||
// which is why no flag-timing change could separate them and why the fix compares
|
||||
// payloads. A flag-timing redesign was built and measured first: it fixed this and
|
||||
// duplicated on IBus, which is why it is not what landed.
|
||||
// See .tmp/ime-handoff/swarm-scratch/lane-group-e-redesign/.
|
||||
//
|
||||
// CORRECTION to an earlier claim in this file that no Japanese DOM composition trace
|
||||
// exists in the corpus. One does, filed under the Linux bundles rather than the bundle
|
||||
@@ -60,15 +64,18 @@
|
||||
// implementation from HEAD's — measurements here do not transfer to it.
|
||||
// 2. Measured, not inferred: every expectation below was read off onData against the
|
||||
// @xterm/xterm this repo installs (src/browser/input/CompositionHelper.ts,
|
||||
// sha256 10893b3e609b3a1d296e03be03e397b5044308d7f7b6a3da6efd06a545c13a21).
|
||||
// sha256 d6393a7e805139c1b8791a8996a42b7683e7bb0e03c137e98a021917605c1635, under
|
||||
// patch_hash=8a8976e1ddd73b3747547f119f76a72f2fa3f8e6efc6e6134b267d9c7f80f65d —
|
||||
// the store holds one directory per patch iteration, so the hash alone is ambiguous).
|
||||
// The comparison bundle is cited, NOT imported — a landed test can only exercise
|
||||
// code that ships. Stock 6.1.0-beta.287 CompositionHelper.ts,
|
||||
// sha256 1e935e66830ca171456466987cb45ed0a270553901729f11dfa91f6b702e0845
|
||||
// (sha1 ebffd1d354428143d712124f92fbcd846e6e44d4, byte-identical across beta.287,
|
||||
// .288 and .292, so this is also what VS Code 1.129.1 runs). Against that bundle
|
||||
// the duplication is version-NEUTRAL — defective on both, in different magnitudes.
|
||||
// The swallowed insertText is NOT: stock delivers the syllable and this bundle
|
||||
// drops it, making it the one defect here that is ours rather than inherited.
|
||||
// The swallowed insertText was NOT: stock delivered the syllable and this bundle
|
||||
// dropped it, making it the one defect here that was ours rather than inherited.
|
||||
// That arm now asserts parity with stock.
|
||||
// 3. Inferred, not measured: that a real macOS IME delivers a compositionend for a
|
||||
// composition it kept alive across the Cmd. That is ordinary IME behaviour but no
|
||||
// capture contains the gesture, so the trigger is unverified on hardware.
|
||||
@@ -82,8 +89,8 @@
|
||||
// during composition is keyCode 229 in every capture, and 229 returns early.
|
||||
// Do not reintroduce a Space arm.
|
||||
//
|
||||
// These assertions pin CURRENT broken behaviour. When someone fixes it they will fail —
|
||||
// update the expectations to the correct values named in each comment. Do not work
|
||||
// The Cmd arms still pin CURRENT broken behaviour. When someone fixes them they will fail
|
||||
// — update the expectations to the correct values named in each comment. Do not work
|
||||
// around them.
|
||||
import { Terminal } from '@xterm/xterm'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -225,7 +232,7 @@ describe('xterm CompositionHelper — overlapping and swallowed commits at onDat
|
||||
expect(emitted).toEqual(['한'])
|
||||
})
|
||||
|
||||
it('swallows an uncomposed insertText that lands in the sending window', async () => {
|
||||
it('delivers an uncomposed insertText that lands in the sending window', async () => {
|
||||
const { emitted, textarea } = openTerminal()
|
||||
dispatchProcessKeydown(textarea)
|
||||
dispatchCompositionEvent(textarea, 'compositionstart')
|
||||
@@ -242,9 +249,10 @@ describe('xterm CompositionHelper — overlapping and swallowed commits at onDat
|
||||
await nextEventLoop()
|
||||
await nextEventLoop()
|
||||
|
||||
// Whole syllable lost. CORRECT would be ['문', '제'], which is what pristine
|
||||
// beta.287 emits — this arm, unlike the ones above, is ours.
|
||||
expect(emitted).toEqual(['문'])
|
||||
// Was ['문'] — the whole syllable lost. '제' is not what the deferred send emitted, so
|
||||
// the dedup no longer claims it. Matches pristine beta.287; this arm, unlike the ones
|
||||
// above, was ours to fix.
|
||||
expect(emitted).toEqual(['문', '제'])
|
||||
})
|
||||
|
||||
it('leaves ordinary Latin typing untouched', async () => {
|
||||
|
||||
+22
-6
@@ -25,8 +25,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
// a browser-dispatched `input` event. It is the harder case for the guard: the +149 ms arm below
|
||||
// passes with `composed` either way, and only the keydown-in-flight arm depends on it.
|
||||
//
|
||||
// These tests pin CURRENT, DEFECTIVE behaviour. #11504 is unfixed at HEAD (PR #11506 is open and
|
||||
// unmerged) and the first test asserts the unwanted period reaches the PTY. A fix must update it.
|
||||
// The first two tests pin CURRENT, DEFECTIVE behaviour. #11504 is unfixed at HEAD (PR #11506 is
|
||||
// open and unmerged) and the first test asserts the unwanted period reaches the PTY. A fix must
|
||||
// update it. The third test no longer pins a defect; see its own docblock for why it moved.
|
||||
|
||||
const HANGUL = '아'
|
||||
const COMMITTED = '아 '
|
||||
@@ -180,19 +181,34 @@ describe('#11504 macOS automatic period substitution after a Hangul space commit
|
||||
terminal.dispose()
|
||||
})
|
||||
|
||||
it('absorbs a substitution that lands before the commit send window drains', async () => {
|
||||
// UPDATED to a new contract, and deliberately NOT a weakening of the reporter's row above.
|
||||
//
|
||||
// This arm never covered the report. Its own prior note said as much: what it measured was that
|
||||
// "Orca's `handleCompositionInput` intercept still owns the path here and swallows it" — the
|
||||
// incidental reach of that intercept's window at a timing the reporter never observed, not
|
||||
// coverage #11504 depends on. The absorb came for free with the intercept; nobody chose it.
|
||||
//
|
||||
// What changed in the intercept: it discarded ANY insertText landing in the sending window, so it
|
||||
// also ate ordinary keystrokes typed one macrotask after a Japanese conversion commit — type a
|
||||
// segment, convert, press `a`, lose the `a`. It now discards only a payload equal to what the
|
||||
// deferred send actually emitted, so a different payload gets through. This substitution is a
|
||||
// different payload, hence the flip. See terminal-ime-xterm-composition-commit-overlap.test.ts.
|
||||
//
|
||||
// The reporter's timing is untouched. The +149 ms arm at :141 lands long past both drains, never
|
||||
// reached this intercept, and still asserts the unwanted period arriving at the PTY. #11504 stays
|
||||
// unfixed at HEAD and its fix is not in xterm: PR #11506 is open and unmerged and changes only
|
||||
// src/main/ (index.ts plus macos-automatic-period-substitution.ts and its test).
|
||||
it('delivers a substitution that lands before the commit send window drains', async () => {
|
||||
const { emitted, terminal, textarea } = openTerminal()
|
||||
replayRecordedCommit(textarea)
|
||||
await nextTask()
|
||||
expect(emitted).toEqual([COMMITTED])
|
||||
|
||||
// Same payload, same guards, only earlier: Orca's `handleCompositionInput` intercept still owns
|
||||
// the path here and swallows it. The delay in the report is a trigger condition, not a detail.
|
||||
substitutionInput(textarea, '아. ')
|
||||
await nextTask()
|
||||
await nextTask()
|
||||
|
||||
expect(emitted).toEqual([COMMITTED])
|
||||
expect(emitted).toEqual([COMMITTED, SUBSTITUTION])
|
||||
terminal.dispose()
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user