mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 00:04:01 +00:00
* fix(terminal): hold a cursor chord until the composing syllable commits The composed glyph reaches the pty from the composition session-end handler, which runs after the chord's keydown. Only Enter was held for that, so every other chord went straight out on the transport and overtook the text it was typed after: with 가나 on the line, typing 가나다 and pressing Cmd+Left left 다가나, the composing 다 landed at the cursor's destination. Defer any sendInput chord while a composition is live or its session has not yet flushed. Korean 2-Set shows the shape most clearly — the platform replays the chord unmarked after keyup, so isComposing is already false while the session is still pending. No fallback timer on this path. A newline arriving late still arrives, which is what that timer is for; a chord arriving mid-preedit is the corruption the wait exists to prevent, and a conversion can hold its candidate window open for seconds. Dropping the chord costs one keypress, firing early costs a line. Pane commands are unaffected: they are not sendInput actions. Fixes #12871 * test(e2e): pin the composing-chord order at the pty The unit coverage asserts the handler's ordering against a synthetic transport. This asserts it where it is actually observable: the committed glyph and the chord reach the pty by two different routes, and only their merged order is visible to the shell. Verified to discriminate — against keyboard-handlers.ts from main the same spec reads 01 eb8ba4 0a, the chord ahead of the syllable, which is the reported corruption byte-for-byte. * refactor(terminal): add the composing-chord deferral without touching the Enter path Nesting the new branch inside the Enter condition re-indented the whole Enter block, which is the kind of diff that can silently change it. Keeping them as sibling conditions leaves the Enter path out of the diff entirely. * test(e2e): pin the renderer to macOS for the Cmd+Left chord Cmd+Left resolves to \x01 only under the macOS branch of the shortcut policy, so on a Linux shard the chord produced no byte and the spec passed by measuring nothing — it failed in CI for that reason, not for the behaviour under test. Pinning the platform is the established pattern for these specs, and expectImePlatformPolicy fails loudly if the override does not take.
88 lines
3.7 KiB
TypeScript
88 lines
3.7 KiB
TypeScript
/**
|
|
* #12871: a cursor chord pressed while a syllable is still composing reached the pty ahead of the
|
|
* text it was typed after. With `가나` on the line, typing `가나다` and pressing Cmd+Left left
|
|
* `다가다` — the composing `다` landed at the cursor's destination.
|
|
*
|
|
* The unit coverage in `keyboard-handlers-ime-composing-chord.test.tsx` pins the handler's
|
|
* ordering against a synthetic transport. This asserts the same ordering at the pty, where the
|
|
* committed glyph and the chord arrive by two different routes — the composition session-end
|
|
* handler and the transport — and only their merged order is observable.
|
|
*/
|
|
import { expect, test } from './helpers/orca-app'
|
|
import { closeTerminalImePaneArena, openTerminalImePaneArena } from './terminal-ime-pane-arena'
|
|
import {
|
|
commitImeText,
|
|
composeHangulSyllable,
|
|
dispatchPlainEnter,
|
|
type ImeKeyIdentity
|
|
} from './terminal-ime-cdp-composition'
|
|
import {
|
|
createTerminalImeByteReader,
|
|
removeTerminalImeByteReader,
|
|
startTerminalImeByteReader,
|
|
waitForTerminalImeBytes
|
|
} from './terminal-ime-byte-reader'
|
|
import { applyImePlatformPolicy, expectImePlatformPolicy } from './terminal-ime-platform-policy'
|
|
|
|
const JAMO: Record<string, ImeKeyIdentity> = {
|
|
ㄷ: { key: 'ㄷ', code: 'KeyE', keyCode: 229 },
|
|
ㅏ: { key: 'ㅏ', code: 'KeyK', keyCode: 229 }
|
|
}
|
|
|
|
/** ㄷ → ㅏ assembles 다, which stays composing because a final consonant could still follow. */
|
|
const DA_FRAMES = [
|
|
{ jamoKey: JAMO['ㄷ'], preedit: 'ㄷ' },
|
|
{ jamoKey: JAMO['ㅏ'], preedit: '다' }
|
|
] as const
|
|
|
|
/** Cmd+Left resolves to Ctrl+A (readline start-of-line) — the chord from the report. */
|
|
const CMD_LEFT_BYTE = '\x01'
|
|
|
|
test.describe('Terminal 2-Set Korean composing-chord order', () => {
|
|
test('sends the composing syllable before a Cmd+Left pressed during it', async ({
|
|
orcaPage,
|
|
testRepoPath
|
|
}, testInfo) => {
|
|
// Cmd+Left resolves to \x01 only under the macOS branch of the shortcut policy, so a Linux
|
|
// runner produces no chord byte at all and the spec would pass by measuring nothing. Pinning
|
|
// the renderer's platform is what lets the reported chord run on any shard; the assertion
|
|
// below fails loudly if the override did not take.
|
|
await applyImePlatformPolicy(orcaPage, 'mac')
|
|
await expectImePlatformPolicy(orcaPage, 'mac')
|
|
|
|
const arena = await openTerminalImePaneArena(orcaPage)
|
|
const reader = createTerminalImeByteReader(testRepoPath, 1)
|
|
let completed = false
|
|
try {
|
|
await startTerminalImeByteReader(orcaPage, arena.ptyId, reader)
|
|
|
|
await composeHangulSyllable(arena.session, orcaPage, DA_FRAMES)
|
|
|
|
// Pressed while the syllable is still in preedit. Before the fix this reached the pty
|
|
// immediately, ahead of the 다 that had been typed first.
|
|
await arena.session.send('Input.dispatchKeyEvent', {
|
|
type: 'rawKeyDown',
|
|
key: 'ArrowLeft',
|
|
code: 'ArrowLeft',
|
|
windowsVirtualKeyCode: 37,
|
|
nativeVirtualKeyCode: 37,
|
|
modifiers: 4
|
|
})
|
|
|
|
await commitImeText(arena.session, '다')
|
|
// The held chord flushes a macrotask after the composition session ends. Enter is only here
|
|
// to terminate the line for the reader, so let the chord land before adding it — otherwise
|
|
// the newline overtakes it and the assertion measures the wrong pair.
|
|
await orcaPage.waitForTimeout(250)
|
|
await dispatchPlainEnter(arena.session)
|
|
|
|
const received = await waitForTerminalImeBytes(orcaPage, reader)
|
|
expect(received).toEqual([Buffer.from(`다${CMD_LEFT_BYTE}\n`).toString('hex')])
|
|
completed = true
|
|
} finally {
|
|
await closeTerminalImePaneArena(arena, testInfo, 'korean-composing-chord-order', !completed)
|
|
removeTerminalImeByteReader(reader)
|
|
}
|
|
})
|
|
})
|