Skip to content

feat(prompts): add scroll indicators and interactive listage module#176

Open
rafa-thayto wants to merge 1 commit intomainfrom
rafa-thayto/listage-improvements
Open

feat(prompts): add scroll indicators and interactive listage module#176
rafa-thayto wants to merge 1 commit intomainfrom
rafa-thayto/listage-improvements

Conversation

@rafa-thayto
Copy link
Copy Markdown
Contributor

@rafa-thayto rafa-thayto commented Apr 16, 2026

Summary

  • Add custom select/search prompts in lib/listage.ts built on @inquirer/core's createPrompt that show ↑ N more above / ↓ N more below scroll indicators when choices overflow the visible page
  • Consolidate the piped-stdin TTY fallback (previously only in lib/prompts.ts for select/confirm, missing for search) and the duplicated filterChoices helper into the shared listage module
  • Add interactive environment picker to clerk switch-env when no argument is given in human mode

Why custom prompts?

@inquirer/select v5 and @inquirer/search v4 removed the old helpMode theme option, and usePagination returns a plain string with no metadata about items above/below the viewport. There's no theme hook or callback to inject scroll indicators without reimplementing the render function via createPrompt.

Commands updated

Command Prompt type What it picks
clerk link search Clerk application
clerk init search Framework, package manager
clerk api (interactive) select API category, endpoint
clerk deploy select Domain type, OAuth flow
clerk switch-env select (new) Environment (when no arg given)

Other improvements

  • search prompts now have the TTY fallback for piped stdin (previously missing — link and init/bootstrap imported search directly from @inquirer/prompts)
  • Removed duplicated filterChoices helper from init/bootstrap.ts (now shared from listage.ts)

Test plan

  • Run clerk api with no args — verify scroll indicators appear on the endpoint list when it exceeds page size
  • Run clerk link — verify app picker shows scroll indicators with many apps
  • Run clerk switch-env with no argument — verify interactive picker appears
  • Run clerk switch-env staging — verify direct argument still works
  • Pipe stdin (echo | clerk switch-env) — verify fallback to text output
  • Verify all CI checks pass (typecheck, lint, tests)

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

This change introduces a new shared utility module (listage.ts) implementing custom select and search interactive prompts built on Inquirer Core, featuring scroll indicators that display when choices extend beyond the visible portion. The module exports filtering utilities and prompt implementations. Multiple CLI commands (deploy, api/interactive, init/bootstrap, link, switch-env) are updated to import these prompts from the new module instead of @inquirer/prompts. The switch-env command gains interactive environment selection via the new picker when called without arguments in human mode. New dependencies (@inquirer/ansi and @inquirer/figures) are added, and test mocking across multiple test files is updated to handle the new module.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly relates to the changeset, detailing custom prompts with scroll indicators and an interactive environment picker across multiple commands.
Title check ✅ Passed The title accurately summarizes the main changes: adding scroll indicators to interactive list prompts and introducing the listage module.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 16, 2026

🦋 Changeset detected

Latest commit: a2fd8f2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
clerk Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@rafa-thayto rafa-thayto force-pushed the rafa-thayto/listage-improvements branch from bde0a77 to 9480dde Compare April 17, 2026 13:27
@rafa-thayto rafa-thayto requested a review from wyattjoh April 17, 2026 16:39
Custom select/search prompts built on @inquirer/core that show
"↑ N more above" / "↓ N more below" when the list overflows the
visible page. Also consolidates the piped-stdin TTY fallback and
the duplicated filterChoices helper into a single listage module.

Commands updated to use the new prompts:
- clerk link (app picker)
- clerk init (framework/PM pickers)
- clerk api (category/endpoint pickers)
- clerk deploy (domain/OAuth pickers)
- clerk switch-env (new interactive env picker when no arg given)
@rafa-thayto rafa-thayto force-pushed the rafa-thayto/listage-improvements branch from 9480dde to a2fd8f2 Compare April 17, 2026 16:54
@rafa-thayto rafa-thayto changed the title feat(prompts): add scroll indicators to interactive list prompts feat(prompts): add scroll indicators and interactive listage module Apr 17, 2026
Copy link
Copy Markdown
Contributor

@wyattjoh wyattjoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — PR #176

Reviewed with Opus + Codex second-opinion validation.

High

H1. TTY fallback does not handle /dev/tty or CONIN$ open failures (codex confirmed)
packages/cli-core/src/lib/listage.ts:42-45 unconditionally calls createReadStream(TTY_PATH) when stdin is not a TTY, without attaching an error listener. In Docker without --tty, detached CI runners, or Windows sessions without CONIN$, the stream emits an async error event that is unhandled. Previously the same flaw existed in lib/prompts.ts but is now extended to search callsites (link, init/bootstrap).

H2. Scroll indicator math ignores rendered-line wrapping (codex confirmed)
scrollBounds() (listage.ts:68-95) counts items, but @inquirer/core's usePagination paginates on rendered lines post-breakLines(). Long clerk api labels or narrow terminals produce wrapped choices, which means the "more above/below" counts and visible-range inference diverge from what is actually shown.

Medium

  • M1. Prompt height jumps 1 line at edges (listage.ts:104-107,115-117): only the non-empty indicator row renders at top/bottom.
  • M2. Math.max(pageSize - 2, 3) plus indicator rows (listage.ts:294,508): callers passing pageSize: 3 or 4 get 4-5 lines.
  • M3. Zero direct tests for lib/listage.ts. Every command mocks it out; scrollBounds, withScrollIndicators, normalizeChoices, isSelectable, the TTY fallback, keypress handlers, and search race-cancellation are all unverified. Add at minimum a lib/listage.test.ts for the pure helpers.
  • M4. ValidationError thrown inside useMemo (listage.ts:216-223) is not guaranteed to be caught by @inquirer/core's validation path; prefer validating up-front in the select() wrapper.
  • M5. search active can be -1 with empty results (listage.ts:417-423,461): the useState<number>() destructure default only applies when the value is strictly undefined, not for the bounds.first = -1 case.
  • M6. cursorHide is appended on every select render (listage.ts:340) with no matching cursorShow on the done branch (listage.ts:319-321). Commands that chain prompts (deploy, api/interactive) can render subsequent prompts with a hidden cursor.
  • M7. Duplicate select API surface: old lib/prompts.ts:36-46 still exports select, and commands/init/skills.ts:17,106-113 still uses it. Two implementations with different appearance and TTY-fallback paths.
  • M8. packages/cli-core/src/commands/switch-env/README.md is not updated despite the new interactive picker behavior (per .claude/rules/commands.md).

Missed on first pass (codex caught)

  • All-disabled list deadlock. listage.ts:217-220 checks isNavigable rather than isSelectable; an all-disabled list never throws and produces a prompt with no selectable item and no exit path.
  • Stuck loading state on search error. listage.ts:427-453 sets status to "loading" before fetch but the error path never resets it to "idle", leaving the prompt loading indefinitely and blocking arrow-key nav (listage.ts:485-494).

Nits

  • isSelectable<T> narrows to { disabled?: false } but runtime accepts any falsy disabled (listage.ts:124-126).
  • rl.clearLine(0) on arrow keys clobbers the type-ahead buffer (listage.ts:251-260).
  • Type-ahead startsWith lacks Unicode normalization (listage.ts:275-278).
  • SelectConfig / SearchConfig types not exported (listage.ts:185-191,378-390).
  • bullet in keysHelpTip may not render in all terminals (listage.ts:201,401).
  • switch-env/index.ts:30 reassigns the function parameter.
  • @inquirer/ansi and @inquirer/figures added as direct deps though already transitive.

Positives

Clear motivation in the PR body (upstream removed helpMode / usePagination metadata). Abort-controller wiring in the search useEffect correctly cancels stale results. The defaultApplied.current ref is a subtle bug fix that is easy to miss. Deduplication of filterChoices from init/bootstrap.ts into the shared module is a net cleanup. Changeset scoped as minor matches the surface-visible change.

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