code-yeongyu / code-yeongyu/web-terminal
iPhone Edge: Korean never composes — the keyboard delivers bare jamo keydowns that ghostty forwards as-is
- Dominant language
- TypeScript
- Stars
- 22
- Forks
- 5
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
On iPhone Edge, Korean input never composes: typing Hangul produces disconnected compatibility jamo instead of syllables. Every jamo is delivered straight to the PTY as a standalone character and no syllable is ever formed.
Intended vs broken output (literal Hangul):
```
intended: 한글
broken: ㅎㅏㄴㄱㅡㄹ
```
### Environment
- web-terminal `3a73f62`, Bun `1.4.0-canary.1`, served over a cloudflared quick tunnel
- iPhone, iOS 26.5.2, Edge (EdgiOS/151.0), system Korean keyboard (2-beolsik)
### Evidence (captured on the device)
An in-page event recorder shipped to the affected device captured the full input pipeline while typing Hangul:
```
keydown key='ㅎ' keyCode=0 isComposing=false prevented=true textarea ''->'' sel 0->0
keydown key='ㅏ' keyCode=0 isComposing=false prevented=true textarea ''->''
keydown key='ㄴ' keyCode=0 isComposing=false prevented=true textarea ''->''
keydown key='ㄱ' ...
keydown key='ㅡ' ...
keydown key='ㄹ' ...
```
No `compositionstart/update/end`. No `beforeinput`. No `input`. Each jamo arrives as a bare `keydown` with the jamo itself in `event.key` and `keyCode 0`.
### Root cause
ghostty-web's keydown handler treats those keydowns as ordinary printable keys: it encodes `event.key` and sends the bare jamo to the PTY, then calls `preventDefault()` (visible as `prevented=true` above). Because the default is prevented, the character never enters the textarea, the field stays empty, and the OS keyboard never gets the field feedback it would need to compose in-field.
Neither of the repo's existing IME paths covers this delivery mode:
- the composition-event path (desktop IMEs) never fires — there are no composition events;
- the `beforeinput` delete+reinsert path in `ime-input.ts` (iOS Safari-style) never fires — there is no `beforeinput`.
### Fix
Compose client-side. A small standard 2-beolsik automaton intercepts bare compatibility-jamo keydowns on the container capture phase (ahead of ghostty's bubble-phase listener), assembles syllables — compound vowels, compound finals, final-consonant migration (dokkaebi-bul), mid-composition Backspace — and replays the result as synthetic composition events. That is exactly the stream a desktop IME produces, so ghostty's `compositionend` sender and the preedit rendering handle everything downstream without modification.
Activation is self-gating per keystroke: a compatibility-jamo `event.key` with `isComposing === false` never occurs with desktop or Android IMEs, which deliver `Process`/229 keydowns and composition events instead.
Verified by replaying the captured device trace in Playwright WebKit (iPhone profile) and confirmed on the physical device: syllables, compound finals, dokkaebi-bul, backspace and word boundaries all compose correctly, and desktop composition/typing is unaffected.
A PR with the implementation follows.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the keydown handler and the existing beforeinput path in ime-input.ts, then review the captured device trace and replay it in Playwright WebKit with the iPhone profile. Done means Korean syllables, compound vowels and finals, final-consonant migration, mid-composition Backspace, and word boundaries work on iPhone Edge without affecting desktop composition or ordinary typing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- playwright, typescript
- Domain
- frontend, mobile, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100