Skip to content

TUI: each prompt keystroke re-registers keymap layers (~15 ms JS per key) #60681

Description

@isaacturnbull

Description

Every keystroke in the TUI prompt costs ~15 ms of JS before anything is drawn. strace shows the main thread on CPU for the whole interval from reading the key to writing the screen, and a JS sampling profile puts about half of it in useKeymapSelector memos.

Cause: several prompt keymap layers compute an eager enabled boolean from per-keystroke state inside their Keymap.createLayer factory, in packages/tui/src/component/prompt/index.tsx:

  • the prompt.clear layer reads store.prompt.text
  • the ! shell-mode, backspace-exits-shell and history ↑/↓ layers read cursorVersion(), which is bumped on every content and cursor change
  • the stashCommands memo reads !!store.prompt.text and returns a new array, so its layer re-registers too

So each key unregisters and re-registers about 6 layers. The keymap emits state, and every useKeymapSelector memo recomputes, including one useShortcuts() memo per caller, each of which rebuilds a formatted Map of every registered command (context/keymap.tsx).

Time from a key written to the TUI's PTY to its first output byte (60 keys, fresh session, three interleaved rounds):

build p50 ms p90 ms
v2.2.1 release 15.9 · 14.4 · 14.7 23.8 · 18.4 · 18.8
v2.2.1 built from source 15.9 · 14.2 · 18.7 21.8 · 17.5 · 24.3
same + lazy enabled (PR) 3.8 · 3.6 · 3.9 5.8 · 4.4 · 7.1

It doesn't depend on session size. The code is unchanged in v2.2.4 and on dev.

Plugins

none (reproduced with an empty config)

OpenCode version

2.2.1 (same code in 2.2.4 and dev)

Steps to reproduce

  1. Run opencode serve and attach the TUI to a session on a PTY you control.
  2. Write single printable characters to the PTY ~200 ms apart (never submit).
  3. Time each write to the TUI's next output byte: ~15 ms p50. A shell on the same PTY echoes in <1 ms.

Operating System

Ubuntu 24.04 (x86_64, 8 vCPU)

Terminal

PTY driven by a test harness; also felt over ssh in iTerm2

Activity

  1. isaacturnbull commented on Oct 30, 2026

    @isaacturnbull
    Author

    Fix: #60682

  2. opencodejr commented on Oct 30, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report and the PR. I could reproduce this.

    I ran the TUI on a PTY with an empty config, wrote 60 single characters 200 ms apart without submitting, and timed each write to the first output byte:

    build p50 ms p90 ms
    2.2.1 release 9.9 · 9.7 · 10.5 14.7 · 14.0 · 18.2
    latest dev (bf5d12c), from source 10.1 · 10.8 18.3 · 14.5
    dev + #60682 4.4 · 4.2 5.9 · 5.1

    The absolute numbers are lower than yours (probably a faster machine), but the gap is the same. That fits the prompt keymap layers re-registering on every keystroke, and it still happens on the latest dev. The script below is the PTY harness I used. The recording shows the typing session, though a delay this small doesn't really show up on screen.

    Related: #60682 (your fix) and #59294 (keeping keymap command objects stable).

    recording.mp4
    Reproduction script (latency.py)
    #!/usr/bin/env python3
    """Measure TUI keystroke latency: time from writing one printable char to the PTY
    until the TUI's first output byte. Usage: latency.py <cmd...>"""
    import os, pty, sys, time, select, struct, fcntl, termios, statistics, tempfile
    cmd = sys.argv[1:] or ["opencode"]
    home = tempfile.mkdtemp(prefix="oc-lat-")
    env = dict(os.environ, TERM="xterm-256color", HOME=home, XDG_CONFIG_HOME=home+"/.config",
               XDG_DATA_HOME=home+"/.data", XDG_STATE_HOME=home+"/.state", XDG_CACHE_HOME=home+"/.cache")
    pid, fd = pty.fork()
    if pid == 0:
        os.execvpe(cmd[0], cmd, env)
    fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", 40, 120, 0, 0))
    def drain(t):
        end = time.monotonic() + t
        while time.monotonic() < end:
            r,_,_ = select.select([fd],[],[],0.05)
            if r:
                try: data = os.read(fd, 65536)
                except OSError: return
                # answer terminal queries minimally
                if b"\x1b[6n" in data: os.write(fd, b"\x1b[1;1R")
    drain(12)  # let the TUI boot
    lat = []
    for i in range(60):
        drain(0.2)
        t0 = time.monotonic()
        os.write(fd, b"abcdefghij"[i % 10:i % 10 + 1])
        r,_,_ = select.select([fd],[],[],2)
        if r:
            lat.append((time.monotonic()-t0)*1000); os.read(fd, 65536)
    os.kill(pid, 9)
    lat.sort()
    print(f"n={len(lat)} p50={statistics.median(lat):.1f}ms p90={lat[int(len(lat)*0.9)]:.1f}ms min={lat[0]:.1f}ms")

    Run it: python3 latency.py

  3. self-assigned this
    on Oct 31, 2026
  4. simonklee commented on Oct 31, 2026

    @simonklee
    Member

    This will be fixed soon, thanks for reporting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions