code-yeongyu / code-yeongyu/web-terminal

Firefox: keystrokes typed before the terminal is ready open quick find, which then swallows the whole session

Open
#1 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
22
Forks
5
PR merge metrics
No merged PRs in 30d

Description

### Summary

On Firefox with *Search for text when you start typing* enabled, keys pressed between submitting the login form and ghostty's WASM VT finishing its load land on `document.body`. That opens Firefox's quick find, and the find bar keeps focus afterwards, so every subsequent keystroke goes to the search box instead of the PTY. The terminal renders correctly but accepts no input for the rest of the session.

### Environment

- web-terminal `3a73f62`, Bun `1.4.0-canary.1`
- Firefox 153 (reproduced with Playwright, `accessibility.typeaheadfind=true`)
- Ubuntu 24.04, served over a cloudflared quick tunnel

### Reproduction

1. Enable *Search for text when you start typing* in Firefox settings.
2. Open web-terminal and submit the password.
3. Start typing immediately, before the terminal canvas paints.

### Observed

`document.activeElement` stays `BODY` through the login round trip and the WASM load:

| condition | activeElement while typing | input reaches PTY |
|---|---|---|
| no asset delay | `BODY`, then `MAIN.terminal` | no |
| wasm/woff2 delayed 3s | `BODY` throughout | no |
| retyped after ready | `MAIN.terminal` | yes |

The window is not only a slow-link artifact. argon2id verification (64 MiB, t=3) keeps `/api/login` busy for a few hundred milliseconds on its own, and a tunnel widens it further. Chromium hides the bug because it has no quick find, so the lost keys simply vanish.

An earlier attempt that installed the guard inside `renderApp()` still failed 3 out of 3 runs, because the login round trip completes before that point.

### Cause

Nothing focusable exists between the login submit and `createTerminalApp` resolving, so no element absorbs keys during that window.

### Suggested fix

Route stray printable keys to the PTY rather than only suppressing them, and hold the ones typed before the terminal exists:

```ts
function routeStrayKeyToTerminal(event: KeyboardEvent): void {
if (event.ctrlKey || event.metaKey || event.altKey) return
if (event.key.length !== 1) return
if (isEditable(event.target)) return
if (event.key === " " && activatesOnSpace(event.target)) return
event.preventDefault()
if (activeTerminal === undefined) {
if (pendingKeys.length < PENDING_KEY_LIMIT) pendingKeys.push(event.key)
return
}
activeTerminal.terminal.textarea?.focus()
activeTerminal.sendKeys(event.key)
}

async function boot(): Promise {
document.addEventListener("keydown", routeStrayKeyToTerminal, true)
// ...
}
```

Pair it with `created.terminal.textarea?.focus()` once the terminal is constructed, and flush the buffer from whichever of terminal-ready and socket-connected lands second — `connection.sendInput()` drops silently unless the socket is `OPEN`.

An earlier revision of this issue proposed narrowing the guard to `document.body` and only calling `preventDefault()`. That version is wrong on both counts: it misses focus parked on chrome buttons, and it eats keys typed during the load instead of delivering them. See the follow-up comment for the measurements.

### Verification

Checked on both engines with Playwright:

| check | Firefox | Chromium |
|---|---|---|
| pre-ready keys neutralised, 3 consecutive runs with 3s asset delay | pass | — |
| typing and Enter after the terminal is ready | pass | pass |
| password field accepts real keystrokes | pass | pass |
| `Space` on a focused button still activates it | pass | pass |
| `Ctrl`-modified keys pass through | pass | pass |

`bun run typecheck` and `biome check` are clean. `bun test` is 101/102; the single failure, `SessionStore > kill terminates the session and reports exit`, fails identically on an unmodified checkout in this environment.

Happy to send a PR if the approach looks right.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at boot and createTerminalApp, then trace terminal construction, readiness, and socket connection order. Verify the key-routing behavior with the described Playwright cases, including delayed WASM/woff2 loading, editable fields, Space on buttons, modified keys, and post-ready typing; done means bun run typecheck and biome check remain clean.

Written by the indexing model from the issue text.

Assessment

Tech stack
playwright, typescript, wasm
Domain
frontend, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.