-
-
Notifications
You must be signed in to change notification settings - Fork 522
Description
Description
When using Prompt.select with choices that have long descriptions that wrap across terminal lines, navigating up/down causes duplicate prompt lines to appear instead of updating in place.
Reproduction
- Create a file
repro.ts:
import { Prompt } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const choice = yield* Prompt.select({
message: "Select option:",
choices: [
{ title: "opt-1", value: "a", description: "This is a very long description that will wrap to multiple lines in a narrow terminal window causing rendering issues" },
{ title: "opt-2", value: "b", description: "Another long description that exceeds typical terminal width and causes wrapping when displayed" },
{ title: "opt-3", value: "c", description: "Yet another verbose description to demonstrate the rendering bug in the select prompt" },
],
})
yield* Effect.log(\`Selected: \${choice}\`)
})
program.pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)-
Resize your terminal to < 80 columns (narrow enough that descriptions wrap)
-
Run:
npx tsx repro.ts -
Press up/down arrows to navigate between options
Expected: Prompt updates in place, single line for "Select option:"
Actual: Each navigation prints a new prompt line:
? Select option: ›
? Select option: ›
? Select option: ›
? Select option: ›
Root Cause
In packages/cli/src/internal/prompt/select.ts, the handleClear function calculates lines to erase incorrectly:
const text = "\n".repeat(Math.min(options.choices.length, options.maxPerPage)) + options.message
const clearOutput = InternalAnsiUtils.eraseText(text, columns)Problem 1: Uses empty newlines to represent choices. eraseText counts 1 row per empty line, but actual rendered choices have content (prefix + title + description) that may wrap to multiple terminal rows.
Problem 2: The clear handler ignores state:
clear: () => handleClear(opts) // state ignoredBut descriptions are only rendered for the selected choice (in renderChoiceDescription):
if (!choice.disabled && choice.description && isSelected) { ... }Without knowing state, handleClear can't determine which choice line includes a description and will wrap.
Expected Behavior
The prompt should clear and redraw cleanly when navigating, regardless of description length or terminal width.
Environment
- @effect/cli version: 0.72.1
- Platform: macOS/Linux
- Terminal: Any terminal with width < total line length