Skip to content

feat(tui): paint an editable prompt before the TUI loads - #53698

Closed
Nowaker wants to merge 2 commits into
anomalyco:devfrom
Nowaker:provisional-prompt
Closed

Nowaker wants to merge 2 commits into
anomalyco:devfrom
Nowaker:provisional-prompt

Conversation

@Nowaker

@Nowaker Nowaker commented Oct 7, 2026 •

Copy link
Copy Markdown

Note: I'm aware v1 is now the maintenance branch and new features go to v2 only. This PR is here for others who want to use it on v1. I will submit a v2 version once I've migrated my plugin setup to v2.

Issue for this PR

Closes #53696

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Starting opencode leaves a blank terminal for several seconds while the CLI, plugins and providers load, and anything typed meanwhile is echoed by the terminal and lost. With this PR the screen you are about to get, with a working prompt, appears within about 0.1 s, so you can start typing the moment you launch it:

  • opencode paints the home screen: logo, prompt, shortcuts, directory and version.
  • opencode --session ID and --continue paint the session screen: the prompt at the bottom, a loading line where the transcript goes, and the sidebar when the terminal is wide enough.
  • The prompt is a real editor: the input_* keybinds (with tui.json overrides), word wrap, mouse clicks to move the caret, selection, paste and undo. When the TUI takes over, the text, caret and selection carry over exactly.
  • Agent, model, variant and colors come from a small cache of what the TUI last showed, kept next to the database (opencode.tui-startup.json beside opencode.db). A damaged cache only means less is painted early; it never stops opencode from starting.
  • Enter while loading shows "OpenCode still loading, will submit shortly" and sends the prompt once loaded, but only if the agent, model and variant that load match what was shown. Otherwise it shows "Not submitted: agent/model/variant changed" and waits for Enter again.
  • With --mini, or with the early screen turned off, keys typed during startup are still kept and land in the prompt; an Enter typed during startup is dropped.
  • Two tui.json settings, both on by default and documented in tui.mdx: startup.instant_prompt and startup.early_input.

How: src/boot.ts is the new compiled entrypoint. It switches the terminal to raw mode and paints the screen with plain file and terminal I/O (packages/tui/src/instant/), and a worker thread reads and paints keys while the main thread is still loading the CLI. Just before the renderer starts, the worker hands the prompt state back; the renderer keeps drawing the same frame until the real prompt mounts and takes the text. The home, session and prompt layout numbers both sides depend on moved to a constants-only layout.ts, and the keybind defaults to config/keybind-definitions.ts, so the early screen and the TUI cannot drift apart.

Worth a maintainer's look: the new entrypoint and second worker in script/build.ts, and InstantPrompt.dismiss() in UI.print and the yargs middleware, which restores the terminal before any other command or error writes.

How did you verify your code works?

  • bun typecheck in packages/core, packages/tui and packages/opencode; bun test in packages/tui passes, including test/instant/*. One test runs every editing action from every caret position through both the new editor and opentui's TextareaRenderable and requires the same text, caret and selection; another paints both screens at terminal sizes from 1x1 to 160x45.
  • A compiled binary in a scratch home on a throwaway tmux socket:
    • The early frame matches the loaded TUI row for row: the home screen, and the session screen at 160x45 (with the sidebar) and 120x40 (without).
    • Typing, arrow keys and clicks before load carry over with the caret in place.
    • Enter before load submits after load; with the cache edited to another model it shows the red notice instead.
    • A cache with wrong field types, --help, subcommands, --mini and startup.instant_prompt: false all start normally.
  • Time until typed text appears, --pure (Linux: median of 10 launches; Macs: range of 4-6 launches):
host opencode opencode -s ID opencode -c
Linux x64, 18 cores, load ~27 4778 → 114 ms 5998 → 115 ms 5545 → 160 ms
Apple M2 Pro 72-74 ms after (before not measured) 2665-3466 → 68-138 ms 2163-2542 → 71-75 ms
Apple M4 Max 71-73 ms after (before not measured) 2162-3062 → 46-52 ms 1762-2538 → 49-53 ms

Screenshots / recordings

Recording to follow.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

This PR is AI generated, but under direct supervision and on request of @Nowaker.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Nowaker added a commit to Nowaker/opencode that referenced this pull request Oct 7, 2026
Adds the feature record for the startup instant prompt (b12845f)
and the session record for its stage 1: landing conflicts, upstream
issue anomalyco#53696 and PR anomalyco#53698, timings on desktop, m2pro and m4max, and
the 1.18.34-vt-144-907b3bc518 installs on desktop and m4max.

AI-Tool: opencode 1.18.34-vt-128-907b3bc518
AI-Model: anthropic/claude-opus-5-5
AI-Variant: high
AI-Platform: linux
AI-Harness: Vibeterm cfa71a2
AI-Session-ID: ses_eebf9a83fffeIQQwW9gVFo5jaO
`opencode` used to show a blank terminal for seconds while it loaded the
CLI, the server worker, plugins and providers, and keys typed meanwhile
were echoed by the tty and lost or turned into stray Enters. The home
screen with a working prompt is now on screen within about 100 ms.

boot.ts, the new compiled entrypoint, calls InstantPrompt.start() before
it imports the CLI. For the plain TUI (and `--mini`) it puts the tty in
raw mode, reads tui.json with plain file I/O, paints the home screen
from the startup cache, and hands stdin to a worker thread
(instant/worker.ts). The main thread cannot service stdin while it
evaluates the rest of the CLI, which takes 0.3-1.7 s; the worker reads
and paints keys as they are typed. Just before the renderer claims the
terminal, the TUI awaits handover(): the worker stops and returns the
prompt state and unparsed bytes, which are unshifted onto stdin.
A box on the renderer root (component/instant-screen.ts) keeps drawing
the same frame, independent of the providers that render nothing until
they load, and the renderer feeds keys back in until the home Prompt
mounts and claim()s the text, caret and selection (placed through cell
offsets, instant/caret.ts).

The prompt is a real editor (instant/editor.ts): every input_* keybind,
including tui.json overrides, word-wrap layout, click-to-caret, drag
selection, paste and undo. Its actions are checked against opentui's
own TextareaRenderable from every caret position (editor.test.ts).

Enter while loading queues the submit with a white "still loading"
notice; once the TUI has resolved the agent, model and variant it
submits only if they match what the screen showed, otherwise a red
"not submitted" notice asks for Enter again. Without the screen
(`startup.instant_prompt: false`, `--mini`) the same capture runs
invisibly (`startup.early_input`). A line feed in the first read is an
Enter typed before raw mode (ICRNL) and is dropped.

The startup cache (agent, model, variant, colors, shortcuts, branch,
tips height per directory) is written beside the database the process
would open, `<db>.tui-startup.json`, so a different OPENCODE_DB, data
directory or channel never shares it; the loading path does not open
SQLite. DatabaseLocation and GlobalPath split the path logic out of
database.ts and global.ts for that. Other opencode versions and hand
edits can write that file too, so read() keeps only fields of the
expected type, and boot.ts starts opencode without the instant prompt
if starting it throws: a damaged cache paints less, it never stops
opencode from starting.

Layout numbers the screen depends on moved to constants-only modules
read by both sides: layout.ts (home and prompt geometry, placeholders,
spinner, notices) and config/keybind-definitions.ts (keybind defaults).

Measured with opencode-idle-bench ttp, --pure, desktop at load ~27:
typable 114 ms median (85-145) against 4778 ms (3830-5515) before.
The instant prompt now covers opening an existing session as well as
the home screen. With `-s ID` or `-c`, the first frame is the session
screen at the positions routes/session lays it out:
- the prompt at the bottom of the transcript column, with the cwd on
  its status row,
- a "Loading session…" spinner where the transcript starts,
- the sidebar at its real width when the terminal is wide enough and
  the kv `sidebar` setting allows it: the session's cached title (its
  ID until it has one), the ID line, a loading spinner, and the footer
  directory and version.
Typed text, caret and selection hand over to the session's Prompt.
`-c` cannot know the session before load, so its title appears then.

Enter while loading shows the white notice on the right of the status
row, where the usage and shortcuts sit for a session. The queued submit
waits for the session's messages, which set its agent and model, then
compares them with the cache. The startup cache gains a per-session
entry (selection, title, subagent flag, usage line) and the theme's
success color, both checked on read like the rest of the cache.

The sidebar rule (wider than 120 columns, hidden by kv, never for a
subagent), the content width and the session and sidebar padding moved
to layout.ts, which routes/session, the sidebar and its footer plugin
now read, so the loader cannot drift from them.

While the renderer draws the frame, the grid is rebuilt only after an
edit or a resize; spinner ticks change two cells. Each row is drawn as
runs of equally styled cells.
@Nowaker
Nowaker force-pushed the provisional-prompt branch from 620cd1e to f829a6a Compare October 7, 2026 09:38
@Nowaker Nowaker changed the title feat(tui): paint an editable home prompt before the TUI loads feat(tui): paint an editable prompt before the TUI loads Oct 7, 2026
@thdxr

thdxr commented Oct 10, 2026

Copy link
Copy Markdown
Member

This branch has been upgraded to V2. We're no longer taking PRs for V1. If you think this is still relevant, please port it over and open it for V2.

— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

@thdxr thdxr closed this Oct 10, 2026
Nowaker added a commit to Nowaker/opencode that referenced this pull request Oct 11, 2026
Adds the feature record for the startup instant prompt (b12845f)
and the session record for its stage 1: landing conflicts, upstream
issue anomalyco#53696 and PR anomalyco#53698, timings on desktop, m2pro and m4max, and
the 1.18.34-vt-144-907b3bc518 installs on desktop and m4max.

AI-Tool: opencode 1.18.34-vt-128-907b3bc518
AI-Model: anthropic/claude-opus-5-5
AI-Variant: high
AI-Platform: linux
AI-Harness: Vibeterm cfa71a2
AI-Session-ID: ses_eebf9a83fffeIQQwW9gVFo5jaO
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Paint an editable home prompt before the TUI finishes loading

2 participants