Skip to content

feat(run): add watch-mode keyboard shortcuts#38

Open
bgelatti wants to merge 3 commits into
feat/entity-commandsfrom
feat/watch-shortcuts
Open

feat(run): add watch-mode keyboard shortcuts#38
bgelatti wants to merge 3 commits into
feat/entity-commandsfrom
feat/watch-shortcuts

Conversation

@bgelatti

@bgelatti bgelatti commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Problem

While tagoio run <analysis> was active, the user had no way to interact with the running process beyond Ctrl-C. There was no manual restart, no way to clear the screen between runs, no in-session help, and no escape hatch for a hung cleanup. The dev loop felt worse than every comparable tool (vitest, nodemon, wrangler dev).

Investigation

  • Surveyed how the best-in-class tools handle this: vitest and jest both build their watch shortcuts on native readline.emitKeypressEvents + process.stdin.setRawMode. The dedicated npm packages (keypress, ink, terminal-kit, blessed) are all either unmaintained, full TUI frameworks with 25+ transitive deps, or prompt libraries that do not support background listening. Rolling our own on the Node primitives is the proven path — no new dependencies.
  • Once shortcuts were wired up, live smoke surfaced two latent bugs in the existing spawn that the keystroke layer made visible: 1) shell: true meant kill() only killed the wrapper shell, leaving the tsx/deno/tsnd runtime running as a zombie that kept printing (looked like a phantom restart on every key); 2) stdio: "inherit" let keystrokes leak from the parent's terminal into the child, which tsx then interpreted as its own watch-mode rerun trigger.
  • clig.dev was a hard constraint: be interactive only on a TTY, provide an opt-out, exit quickly on Ctrl-C, no new conventions where shell conventions exist.

Solution

A thin parent-side keystroke layer that owns the child lifecycle:

  • Shortcuts: q (quit), h / ? (help), r (restart, full re-spawn), c (clear screen). First Ctrl-C is a graceful quit; a second Ctrl-C within 2s force-exits with code 130.
  • Safety: installs only when stdin is a TTY; --no-interactive flag opts out (CI, piped logs, Docker). Non-TTY runs are unchanged from before.
  • Reliability: exec prefix on the spawned command so killing the child PID actually kills the analysis runtime, not just the wrapper shell. Child stdin is set to ignore while shortcuts are active, so keys cannot leak through to tsx.
  • Implementation: ~140 LOC in src/lib/watch-shortcuts.ts on native Node APIs; zero new dependencies. run-analysis.ts is refactored into a respawn loop driven by the shortcut handlers, with cleanup (run_on: "tago") in finally so it fires on every exit path.

Tests

  • Unit: 13 new tests for the keystroke layer (all four keys, double-Ctrl-C in/out of window, unknown-key drop, teardown idempotency, non-TTY path), plus 4 new tests on run-analysis covering restart re-spawn, quit cleanup, --no-interactive skip, and non-TTY skip.
  • Full suite: 610/610 green, 0 new lint warnings.
  • Live smoke: every shortcut verified end-to-end against a real analysis. Both zombie / leak bugs were reproduced, fixed, and re-verified.

Note for reviewers

Branched off feat/entity-commands (branch-from-a-branch). PR is targeted there so the diff is scoped strictly to the watch-shortcuts work — review against `feat/entity-commands`, not `master`.

bgelatti and others added 2 commits June 3, 2026 10:47
Adds a parent-side keystroke layer (q/h/?/r/c plus double-Ctrl-C
force-quit) on top of tsx/deno/tsnd's existing file watcher, plus a
--no-interactive opt-out and a TTY guard so CI and piped runs stay
unchanged. The respawn loop owns the child lifecycle with `exec` and
`stdio: ["ignore", ...]` so signals reach the analysis runtime cleanly
and keystrokes never leak to the child as phantom restarts.
The spawned command interpolated scriptPath unquoted, so a project path
containing a space was split into two shell arguments and the runtime
failed to find the file. Wrap the path in quotes and add a regression
test covering a spaced analysisPath.
@mateuscardosodeveloper

mateuscardosodeveloper commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Ran the watch-mode shortcuts end-to-end against a real analysis on a TTY. Verified each path covered by this PR:

  • r — restarts the analysis (full re-spawn), runs repeatedly across runs.
  • c — clears the screen.
  • h / ? — prints the shortcut help block.
  • q — graceful quit; run_on flips back to tago on exit.
  • Ctrl-C once — graceful quit; a second Ctrl-C within 2s force-quits.
  • --no-interactive and non-TTY stdin — shortcuts are not installed, behavior unchanged.

The two latent spawn bugs (the shell: true zombie and the stdin leak into tsx) were reproduced and confirmed fixed: killing the child now terminates the runtime, and keystrokes no longer leak through as phantom restarts.

Also re-verified the path-quoting fix (#38) with a project path containing a space — the analysis launches correctly now.

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.

2 participants