Skip to content

Conversation

@pyramation
Copy link
Contributor

Summary

This PR fixes the keypress duplication issue when multiple Inquirerer instances share the same stdin by implementing a stack-based ownership model. Previously, the SharedInputState would broadcast keypresses to ALL active instances, causing duplicate behavior. Now, only the current owner (top of stack) receives events.

Key changes:

  • Single-owner dispatch: dataHandler now only dispatches to activeStack[top] instead of iterating over all active instances
  • Stack-based ownership: resume() pushes to top (with move-to-top semantics), pause() removes from stack
  • Handler preservation: pause() no longer calls clearHandlers(), fixing the issue where handlers were lost between questions
  • Per-instance Ctrl+C: Ctrl+C now routes through the current owner's process wrapper via new exitProcess() method

Also removes the [create-gen-app] LICENSE updated... log message.

Review & Testing Checklist for Human

  • Critical: Manual test with multiple instances - Create 2+ Inquirerer instances sharing stdin, run list/checkbox prompts sequentially, verify arrow keys move selection once (not twice). This is the core fix and cannot be verified by unit tests alone.
  • Verify handler persistence - Confirm that removing clearHandlers() from pause() doesn't cause handler leaks. Check that handlers are still cleared appropriately at end of each question.
  • Test raw mode cleanup - Verify stdin isn't left in raw mode after all instances are destroyed/paused
  • Test Ctrl+C behavior - Verify Ctrl+C still exits properly with the new owner-based routing

Recommended test plan:

const prompter1 = new Inquirerer({ input: process.stdin, output: process.stdout });
const prompter2 = new Inquirerer({ input: process.stdin, output: process.stdout });

// Use prompter1 for a list question, verify single arrow key response
await prompter1.prompt({}, [{ name: 'test', type: 'list', options: ['a', 'b', 'c'] }]);
prompter1.close();

// Use prompter2, verify it still works correctly
await prompter2.prompt({}, [{ name: 'test2', type: 'list', options: ['x', 'y', 'z'] }]);
prompter2.close();

Notes

  • All 90 inquirerer tests pass, all 28 create-gen-app tests pass
  • The isActive() method now derives state from stack position rather than a boolean flag

Link to Devin run: https://app.devin.ai/sessions/48555739db3d4f1b9e9edfa9ea1e559a
Requested by: Dan Lynch (@pyramation)

- Replace broadcast-to-all-active with single-owner dispatch model
- Add activeStack to SharedInputState for tracking current owner
- resume() now uses move-to-top semantics (removes from stack, pushes to top)
- pause() removes from stack without clearing handlers
- isActive() now checks if instance is top of stack (current owner)
- Ctrl+C handling now routes through current owner's process wrapper
- Remove LICENSE update log message from create-gen-app

This fixes the issue where multiple Inquirerer instances sharing stdin
would all receive keypresses, causing duplicate behavior.
@devin-ai-integration
Copy link

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@pyramation pyramation merged commit 6f5ca3f into main Dec 25, 2025
34 checks passed
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