Repository navigation
hub-ui-onboard: default terminal confirmation is unusable under Nuxt 5's nuxi #448
Description
Summary
Since v1.2.5 (#445), the default onConfirm of @devframes/hub-ui-onboard (confirmInTerminal() on process.stdin) asks before each panel Install. It never installs without consent, but under Nuxt 5's CLI the developer often can't see the question, or can't answer it. #445 expected that "nuxi forks the dev server with an inherited stdin, so the default prompt is expected to work there". That is not what happens with @nuxt/cli-nightly 4.0.1.
What nuxi does
- The first dev server runs inside the nuxi process. Restarts move it to a pre-warmed fork (
ForkPoolin@nuxt/cli-nightly/dist/dev-*.mjs). - In the interactive TUI (the default in a terminal of at least 40x10), the nuxi process holds stdin in raw mode for single-key shortcuts. It captures the dev server's logs, and forks get
stdio: ['ignore', 'pipe', 'pipe', 'ipc']. - In plain mode (
NUXT_TUI=plain, or a small terminal), the nuxi process reads stdin with areadlinefor line shortcuts (r,o,u,qr,y= copy URL,c,q,h). Forks inherit the TTY.
Reproduction
nuxi dev (Nuxt 5 nightly, @nuxt/cli-nightly 4.0.1) under a 120x40 PTY, on a project where @nuxt/devtools-onboard calls createOnboarding() without onConfirm. Install is triggered with a same-origin POST /__devtools/__onboard/install.
| nuxi mode | Dev server runs in | What happens on Install |
|---|---|---|
| TUI, first boot | nuxi process (stdin raw and flowing, owned by the TUI) | DF9005 is folded into the TUI's log capture. The developer only sees "⚠ N warnings". The next keypress answers it as a raw chunk, so pressing w or l to read the warning refuses the install (403). |
| TUI, after any restart | fork, stdin /dev/null |
DF9006, refused at once (403 in 23 ms). Install can never succeed after the first restart. |
| plain, first boot | nuxi process | Works, but y⏎ also runs nuxi's own y shortcut (copy server URL) |
| plain, after a restart | fork that shares the TTY with the parent's readline |
The parent reads the line, so once('data') never fires. The POST and the shared asking promise stay open forever, and the panel stays on "Confirm in your terminal...". |
Possible directions
- When the input is a TTY that something else holds in raw mode (
input.isRaw === true), refuse at once with a clear diagnostic. That beats letting an unrelated keypress answer a question nobody can see. - Don't leave a question that can never be answered open forever: for example, refuse after a bounded wait, so the panel shows
messages.refused. - Fix the nuxi claim in the guide, and recommend that hosts whose CLI owns the terminal pass
onConfirm(ideally something nuxi provides that routes the prompt through its TUI).
Downstream: nuxt/devtools#1126 upgrades to v1.2.5, keeps the default onConfirm (fails closed), and lists this as a known gap. refs nuxt/nuxt#36581
This issue was written with the help of an AI agent.
Activity
antfubot commentedon Oct 14, 2026
Update: nuxt/devtools#1126 no longer keeps the default.
@nuxt/devtools-onboardnow passes its ownonConfirm: () => truebehind a middleware that only lets through requests from this machine: a loopback socket and a localHostname. Other devices get 403. The terminal prompt under nuxi is still the problem described above for any host that relies on the default.Written with the help of an AI agent.