feat(prompts): add scroll indicators and interactive listage module#176
feat(prompts): add scroll indicators and interactive listage module#176rafa-thayto wants to merge 1 commit intomainfrom
Conversation
📝 WalkthroughWalkthroughThis change introduces a new shared utility module ( Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ 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. Comment |
🦋 Changeset detectedLatest commit: a2fd8f2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
bde0a77 to
9480dde
Compare
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)
9480dde to
a2fd8f2
Compare
wyattjoh
left a comment
There was a problem hiding this comment.
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 passingpageSize: 3or4get 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 alib/listage.test.tsfor the pure helpers. - M4.
ValidationErrorthrown insideuseMemo(listage.ts:216-223) is not guaranteed to be caught by@inquirer/core's validation path; prefer validating up-front in theselect()wrapper. - M5.
searchactivecan be-1with empty results (listage.ts:417-423,461): theuseState<number>()destructure default only applies when the value is strictlyundefined, not for thebounds.first = -1case. - M6.
cursorHideis appended on every select render (listage.ts:340) with no matchingcursorShowon the done branch (listage.ts:319-321). Commands that chain prompts (deploy,api/interactive) can render subsequent prompts with a hidden cursor. - M7. Duplicate
selectAPI surface: oldlib/prompts.ts:36-46still exportsselect, andcommands/init/skills.ts:17,106-113still uses it. Two implementations with different appearance and TTY-fallback paths. - M8.
packages/cli-core/src/commands/switch-env/README.mdis 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-220checksisNavigablerather thanisSelectable; 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-453setsstatusto"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 falsydisabled(listage.ts:124-126).rl.clearLine(0)on arrow keys clobbers the type-ahead buffer (listage.ts:251-260).- Type-ahead
startsWithlacks Unicode normalization (listage.ts:275-278). SelectConfig/SearchConfigtypes not exported (listage.ts:185-191,378-390).•bullet inkeysHelpTipmay not render in all terminals (listage.ts:201,401).switch-env/index.ts:30reassigns the function parameter.@inquirer/ansiand@inquirer/figuresadded 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.
Summary
select/searchprompts inlib/listage.tsbuilt on@inquirer/core'screatePromptthat show↑ N more above/↓ N more belowscroll indicators when choices overflow the visible pagelib/prompts.tsforselect/confirm, missing forsearch) and the duplicatedfilterChoiceshelper into the shared listage moduleclerk switch-envwhen no argument is given in human modeWhy custom prompts?
@inquirer/selectv5 and@inquirer/searchv4 removed the oldhelpModetheme option, andusePaginationreturns 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 viacreatePrompt.Commands updated
clerk linksearchclerk initsearchclerk api(interactive)selectclerk deployselectclerk switch-envselect(new)Other improvements
searchprompts now have the TTY fallback for piped stdin (previously missing —linkandinit/bootstrapimportedsearchdirectly from@inquirer/prompts)filterChoiceshelper frominit/bootstrap.ts(now shared fromlistage.ts)Test plan
clerk apiwith no args — verify scroll indicators appear on the endpoint list when it exceeds page sizeclerk link— verify app picker shows scroll indicators with many appsclerk switch-envwith no argument — verify interactive picker appearsclerk switch-env staging— verify direct argument still worksecho | clerk switch-env) — verify fallback to text output