Summary
The zpress dev server does not exit on the first Ctrl+C — it requires pressing Ctrl+C twice to terminate the process.
Current behavior
The dev server's only exit handler is inside the Ink TUI's useInput hook (packages/cli/src/screens/dev-screen.tsx:230-241):
useInput(
(input, key) => {
if (input === 'q' || (key.ctrl && input === 'c')) {
if (watcherRef.current) {
watcherRef.current.close()
}
exit()
process.exit(0)
}
},
{ isActive: isTTY }
)
The dev command is registered with exit: 'manual' mode (packages/cli/src/commands/dev.ts), meaning the Kidd CLI framework expects the screen to handle its own exit. However, there is no process.on('SIGINT', ...) handler registered anywhere in the codebase.
When Ctrl+C is pressed:
- First press — The SIGINT signal is consumed by Ink's default handler, which begins a graceful unmount but does not terminate the process (manual exit mode). The
useInput handler doesn't fire because the signal is intercepted at a lower level.
- Second press — Node's default SIGINT behavior force-terminates the process.
Expected behavior
A single Ctrl+C should gracefully shut down the dev server, close the file watcher, and exit the process.
Location
packages/cli/src/commands/dev.ts — dev command registration with exit: 'manual'
packages/cli/src/screens/dev-screen.tsx:230-241 — useInput exit handler (only TUI-level)
packages/cli/src/lib/watcher.ts:171-179 — watcher cleanup (close + cancel debounced syncs)
packages/cli/src/lib/rspress.ts:77-178 — Rspress dev server start/stop (no SIGINT handler)
Proposed approach
Register a process-level SIGINT handler that invokes the existing cleanup logic — close the file watcher, shut down the Rspress HTTP server, then process.exit(0). The cleanup code already exists; it just needs to be wired up to the signal.
Summary
The zpress dev server does not exit on the first Ctrl+C — it requires pressing Ctrl+C twice to terminate the process.
Current behavior
The dev server's only exit handler is inside the Ink TUI's
useInputhook (packages/cli/src/screens/dev-screen.tsx:230-241):The dev command is registered with
exit: 'manual'mode (packages/cli/src/commands/dev.ts), meaning the Kidd CLI framework expects the screen to handle its own exit. However, there is noprocess.on('SIGINT', ...)handler registered anywhere in the codebase.When Ctrl+C is pressed:
useInputhandler doesn't fire because the signal is intercepted at a lower level.Expected behavior
A single Ctrl+C should gracefully shut down the dev server, close the file watcher, and exit the process.
Location
packages/cli/src/commands/dev.ts— dev command registration withexit: 'manual'packages/cli/src/screens/dev-screen.tsx:230-241—useInputexit handler (only TUI-level)packages/cli/src/lib/watcher.ts:171-179— watcher cleanup (close + cancel debounced syncs)packages/cli/src/lib/rspress.ts:77-178— Rspress dev server start/stop (no SIGINT handler)Proposed approach
Register a process-level SIGINT handler that invokes the existing cleanup logic — close the file watcher, shut down the Rspress HTTP server, then
process.exit(0). The cleanup code already exists; it just needs to be wired up to the signal.