Skip to content

fix(tui): hold install and update screens until their action finishes - #676

Open
eric-engberg wants to merge 3 commits into
sirmalloc:mainfrom
eric-engberg:fix/confirm-dialog-busy
Open

eric-engberg wants to merge 3 commits into
sirmalloc:mainfrom
eric-engberg:fix/confirm-dialog-busy

Conversation

@eric-engberg

Copy link
Copy Markdown
Contributor

What

After you confirm an install, uninstall or global update, the confirm dialog now replaces Yes/No with Working... This may take a moment. and ignores Enter and ESC until the action finishes. The pinned version mismatch screen does the same while its "Update npm global install to v…" item runs: it shows Updating npm global install to v2.2.30... This may take a moment. in place of its options.

Why

These actions run a package manager and then write Claude's settings.json, which takes seconds. Until now the dialog stayed live the whole time, with no sign anything was happening:

  • A second Enter ran everything twice. Install to Claude Code → Pinned global install → npm → Yes, then Enter again while npm runs. A logging npm stub on PATH records two concurrent installs one second apart, and both go on to write Claude's settings:
    17:33:31 npm install -g [email protected]
    17:33:32 npm install -g [email protected]
    
  • ESC didn't cancel anything. ESC during the install showed the install menu again, but the install kept running. When it finished, it wrote settings.json and pulled the TUI from wherever the user had gone to the Install Complete notice.
  • The pinned mismatch screen had the same gap. Two Enters on "Update npm global install to v2.2.30" started two npm install -g runs, and ESC exited the TUI while npm was still running.

How

App runs these actions through one helper that allows a single action at a time. A ref blocks a second start synchronously, before the busy screen has re-rendered, and a state flag drives what's shown. While an action runs:

  • ConfirmDialog takes a new optional busy prop. It keeps the message (so you can still see which command is running), replaces Yes/No with the working line, and ignores ESC. Other dialogs don't pass it, so they behave as before.
  • The pinned mismatch screen takes an updating prop and does the same in place of its Update/Exit list.

When the action settles, its own navigation runs unchanged: success, failure, the "not resolvable on PATH" notice, and the partial-uninstall message all land where they did before. Sync confirm actions (Star on GitHub, the invalid-settings save guard) settle immediately, so nothing changes for them. Ctrl+S was already off on the confirm screen. Ctrl+C still quits.

Demo

No GIF: recording this flow would run a real npm install -g. These are tmux captures of the built CLI under Node with a stub npm that sleeps 8 s. The render mode doesn't affect this screen.

Before: after Yes, the dialog looks unchanged and still takes Enter and ESC.

Continue?

▶  Yes
   No

After: after Yes, then Enter and ESC while npm runs:

Global install command before settings write: npm install -g [email protected]
Final statusLine.command: ccstatusline
Hook command behavior: hook-enabled widgets run ccstatusline --hook

Continue?

Working... This may take a moment.

Testing

  • Two new tests in src/tui/__tests__/App.test.ts render the whole App with stubbed package-manager and Claude settings calls. The setup lives in src/tui/__tests__/helpers/render-app.ts (second commit), so other whole-TUI tests can reuse it. One confirms an install, the other runs the pinned mismatch update; each presses Enter and ESC while the action is pending. On main both fail with runGlobalPackageInstall called 2 times. On this branch both pass, and each asserts one run, the working line, no navigation, and the normal result afterwards. Each passed 10 of 10 runs under Bun and under Node.
  • bun test: 2784 pass, 0 fail. bun run lint passes.
  • App.test.ts under Node (Vitest): 21 pass (19 existing, 2 new). main passes the same 19.
  • Built CLI under Node 26.10.0 in tmux, with a scratch HOME and CLAUDE_CONFIG_DIR and stub npm and claude:
    • Install → Pinned → npm → Yes, then Enter and ESC: main ran npm twice and ESC showed the install menu. This branch ran it once, stayed on the working line, then showed Install Complete.
    • Pinned v2.0.0 install with the v2.2.30 TUI → Update, then Enter and ESC: main ran npm twice. This branch ran it once, showed the updating line, then went to the main menu with ✓ Global package updated and installedVersion: 2.2.30 saved.
  • Bun 1.4.2, Node 26.10.0.

Confirming an install, uninstall or global update started its async action
and left the Yes/No dialog live. A second Enter started the same action
again, so `npm install -g` ran twice at once and both runs went on to write
Claude's settings.json. ESC returned to the previous menu while the action
kept running, and when it finished it jumped the TUI to the main menu (or the
Install Complete notice) from wherever the user had gone. The pinned version
mismatch screen's "Update npm global install" item had the same problem.

App now runs these actions one at a time. While one runs, the confirm
dialog and the mismatch screen show a working line in place of their options
and ignore ESC; a ref blocks a second start even before that screen
re-renders. When the action settles, its own navigation takes over as
before, including the failure paths.
Move the scratch config, probe stubs and Ink rendering used by the App
tests into helpers/render-app.ts, next to wait-for-ink.ts, so other tests
that drive the whole TUI can use the same setup.
The App sets chalk's global color level from the settings it loads, and
the whole-App test helper didn't restore it. Test files that run later in
the same process then rendered at that level instead of their own: a
No Color check could see 256-color codes. The sandbox now restores the
level along with the mocks and the config path.
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.

1 participant