or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-app.mddata-structures.mderrors-edge-cases.mdindex.mdpatterns-recipes.mdprompts-api.mdquick-reference.mdwidgets-api.md
tile.json

errors-edge-cases.mddocs/

Errors & Edge Cases

Known issues, limitations, error handling, and edge case behavior for inquirer-textual.

Known Issues (v0.2.0)

Checkbox enabled Parameter Not Functional

Issue: The enabled parameter in prompts.checkbox() and InquirerCheckbox does NOT work.

# This does NOT work in v0.2.0
result = prompts.checkbox(
    "Select features:",
    ["Auth", "Database", "Caching"],
    enabled=["Auth", "Database"]  # Has no effect
)
# All items will start unchecked regardless of enabled parameter

Impact: All checkbox items start unchecked. User must manually check desired items with spacebar.

Workaround: None. Users must manually select items every time.

Status: Known bug in version 0.2.0.

Error Scenarios

Validation Errors

Behavior: When validation fails on Enter press, prompt remains active and displays validation error. User can continue editing or quit.

from textual.validation import Function

result = prompts.text(
    "Email:",
    validators=Function(lambda s: '@' in s, "Invalid email")
)

# If user enters "test" and presses Enter:
# - Validation fails
# - Error message "Invalid email" displayed
# - Prompt remains active
# - User can continue editing or press Ctrl+D to quit

No Exception: Validation failures do NOT raise exceptions. They just keep the prompt active.

Empty Input

Behavior varies by prompt type:

Text/Secret prompts: Allow empty input by default unless validators prevent it.

result = prompts.text("Name:")
# User can press Enter with empty input
# result.command == 'select', result.value == ''

Number prompts: Do NOT allow empty input. Enter on empty input does nothing.

result = prompts.number("Age:")
# User cannot submit empty - Enter does nothing
# Must enter valid integer or quit with Ctrl+D

Select/Checkbox prompts: Always have a value (highlighted item or checked items).

result = prompts.select("Choose:", ["A", "B", "C"])
# Always returns a value (the highlighted item) on Enter
# Cannot be empty unless mandatory=False and user presses Ctrl+C

User Quits (Ctrl+D)

Behavior: Result has command='quit' and value contains the current state.

result = prompts.text("Name:")
if result.command == 'quit':
    # User pressed Ctrl+D
    # result.value contains partial input if any
    print("User quit")

In multi-prompt: Returns values completed so far.

result = prompts.multi([
    InquirerText("Field 1:"),
    InquirerText("Field 2:"),
    InquirerText("Field 3:")
])

# If user completes Field 1 and 2, then quits:
# result.command == 'quit'
# result.value == ['value1', 'value2']  # Only completed fields

User Presses Ctrl+C

Behavior: Only allowed when mandatory=False. Otherwise, Ctrl+C does nothing.

# mandatory=True (default) - Ctrl+C does nothing
result = prompts.confirm("Continue?")
# User cannot exit with Ctrl+C

# mandatory=False - Ctrl+C allowed
result = prompts.confirm("Continue?", mandatory=False)
if result.command == 'ctrl+c':
    # User pressed Ctrl+C
    # result.value is None
    print("User aborted")

Availability: Only prompts.confirm() and prompts.select() support mandatory parameter. Text/secret/number prompts do not.

RuntimeError in inquiry_func

Scenario: Calling app.prompt() after app.stop() has been called.

def flow(app):
    r1 = app.prompt(InquirerText("First:"))
    app.stop(r1.value)

    # This raises RuntimeError:
    r2 = app.prompt(InquirerText("Second:"))  # Error!

app = InquirerApp()
result = app.run(inline=True, inquiry_func=flow)

Error: RuntimeError: Cannot prompt after app has been stopped

Solution: Do not call app.prompt() after app.stop(). Use early returns.

def flow(app):
    r1 = app.prompt(InquirerText("First:"))
    app.stop(r1.value)
    return  # Exit flow after stop

Edge Cases

Empty Choice List

Behavior: Undefined. Do not pass empty choice list to select/checkbox prompts.

# DO NOT DO THIS
result = prompts.select("Choose:", [])  # Empty list - behavior undefined

Solution: Always validate that choices list is not empty before calling select/checkbox.

choices = get_choices()
if not choices:
    print("No choices available")
else:
    result = prompts.select("Choose:", choices)

Mixing Strings and Choice Objects

Valid: Can mix strings and Choice objects in same list.

from inquirer_textual.common.Choice import Choice

choices = [
    "Plain string",
    Choice("Choice object", data={"id": 1}),
    "Another string"
]

result = prompts.select("Choose:", choices)

# result.value can be either str or Choice
if isinstance(result.value, Choice):
    print(f"Choice: {result.value.name}, Data: {result.value.data}")
else:
    print(f"String: {result.value}")

Caution: When mixing, must check type to safely access Choice.data.

Number Prompt Return Type

Important: prompts.number() returns string, not int.

result = prompts.number("Age:")
if result.command == 'select':
    age_str: str = result.value  # String, not int
    age_int: int = int(result.value)  # Must convert to int

Reason: The value is validated to contain only integer characters but is returned as string for consistency with text prompts.

Choice.command Overrides Result Command

Behavior: When a Choice with custom command is selected, Result.command is that custom command, not 'select'.

from inquirer_textual.common.Choice import Choice

choices = [
    Choice("View", command="view"),
    Choice("Edit", command="edit")
]

result = prompts.select("Action:", choices)

# If user selects "View":
# result.command == 'view', NOT 'select'
# result.value is the Choice object

Pattern: Check for specific commands, not just 'select'.

if result.command == 'view':
    view_item()
elif result.command == 'edit':
    edit_item()

Default Parameter in Select

Behavior: default sets initial highlight position, not pre-selection.

result = prompts.select("Choose:", ["A", "B", "C"], default="B")

# "B" is highlighted initially
# User can navigate away and select a different option
# default does NOT guarantee "B" will be selected

Not a default value: Unlike confirm(default=True), select's default is just the initial cursor position.

Shortcut Conflicts

Behavior: If multiple shortcuts have the same key, the last one wins.

shortcuts = [
    Shortcut('q', 'quit', 'Quit'),
    Shortcut('q', 'quick_save', 'Quick Save')  # Overrides first 'q'
]

result = prompts.text("Input:", shortcuts=shortcuts)
# Pressing 'q' triggers 'quick_save', not 'quit'

Solution: Use unique keys for each shortcut.

Validator Exceptions

Behavior: If a validator raises an exception during validation, it's treated as validation failure.

from textual.validation import Validator, ValidationResult

class BuggyValidator(Validator):
    def validate(self, value: str) -> ValidationResult:
        # This raises exception if value is empty
        first_char = value[0]  # IndexError if empty
        return self.success()

result = prompts.text("Input:", validators=BuggyValidator())
# If user enters empty string, IndexError occurs
# Treated as validation failure

Solution: Handle edge cases in validators.

class SafeValidator(Validator):
    def validate(self, value: str) -> ValidationResult:
        if not value:
            return self.failure("Cannot be empty")
        # Now safe to access value[0]
        return self.success()

None vs Empty String

Behavior: Text prompts return empty string '', not None, when user submits empty input.

result = prompts.text("Name:")
if result.command == 'select':
    # result.value is '', not None
    if not result.value:  # Checks for empty string
        print("Empty input")

When is value None?

  • When mandatory=False and user presses Ctrl+C: result.value is None
  • When inquiry_func calls app.stop(None): result.value is None

Multi-Prompt Partial Values

Behavior: If user quits multi-prompt early, result.value contains only completed values.

result = prompts.multi([
    InquirerText("A:"),
    InquirerText("B:"),
    InquirerText("C:")
])

# If user completes A and B, then quits:
# result.command == 'quit'
# result.value == ['value_a', 'value_b']  # Only 2 values, not 3
# len(result.value) == 2

Pattern: Check length before unpacking.

if result.command == 'select':
    a, b, c = result.value  # Safe - all 3 present
elif result.command == 'quit':
    # Partial data
    if len(result.value) >= 2:
        a, b = result.value[:2]
        # Process partial data

Common Mistakes

Not Checking Command

Mistake: Using result.value without checking result.command.

# WRONG
result = prompts.text("Name:")
process(result.value)  # Bug: might process None or partial data if user quit

Correct:

result = prompts.text("Name:")
if result.command == 'select':
    process(result.value)  # Safe
elif result.command == 'quit':
    print("User quit")

Forgetting to Convert Number Result

Mistake: Using prompts.number() result as integer without conversion.

# WRONG
result = prompts.number("Port:")
start_server(result.value)  # Bug: result.value is string, not int

Correct:

result = prompts.number("Port:")
if result.command == 'select':
    port = int(result.value)  # Convert to int
    start_server(port)

Expecting enabled Parameter to Work

Mistake: Assuming checkbox enabled parameter works.

# WRONG - does not work in v0.2.0
result = prompts.checkbox("Select:", ["A", "B", "C"], enabled=["A", "B"])
# Expects A and B to be checked, but all start unchecked

Correct: Document to user that they must manually check items, or use a workaround with confirm prompts.

# Workaround: Ask for each item individually
items = ["A", "B", "C"]
selected = []
for item in items:
    result = prompts.confirm(f"Enable {item}?", default=True)
    if result.command == 'select' and result.value:
        selected.append(item)

Not Handling Quit in inquiry_func

Mistake: Not checking command in inquiry_func, leading to None values.

# WRONG
def flow(app):
    r1 = app.prompt(InquirerText("First:"))
    r2 = app.prompt(InquirerText("Second:"))
    app.stop((r1.value, r2.value))  # Bug: if user quit r1, r2.value might be unexpected

Correct: Check command and return early.

def flow(app):
    r1 = app.prompt(InquirerText("First:"))
    if r1.command != 'select':
        return  # User quit

    r2 = app.prompt(InquirerText("Second:"))
    if r2.command != 'select':
        return

    app.stop((r1.value, r2.value))

Assuming Select Returns String

Mistake: Assuming select result is always string when using Choice objects.

from inquirer_textual.common.Choice import Choice

choices = [Choice("A", data={"id": 1}), Choice("B", data={"id": 2})]
result = prompts.select("Choose:", choices)

# WRONG
name = result.value.upper()  # Bug: result.value is Choice, not str

Correct: Check type or access Choice.name.

if result.command == 'select':
    if isinstance(result.value, Choice):
        name = result.value.name
        data = result.value.data
    else:
        name = result.value

Exception Handling

No Exceptions for Normal Flow

Important: Inquirer-textual does NOT raise exceptions for normal user actions:

  • User quitting with Ctrl+D: No exception, just command='quit'
  • Validation failures: No exception, prompt stays active
  • User pressing Ctrl+C (when allowed): No exception, just command='ctrl+c'

Exceptions That Can Occur

RuntimeError: Calling app.prompt() after app.stop().

def flow(app):
    r1 = app.prompt(InquirerText("First:"))
    app.stop(r1.value)
    r2 = app.prompt(InquirerText("Second:"))  # Raises RuntimeError

# Solution: Return after stop
def flow(app):
    r1 = app.prompt(InquirerText("First:"))
    app.stop(r1.value)
    return

KeyboardInterrupt: Only if user presses Ctrl+C when NOT allowed (i.e., mandatory=True). But this is handled internally and should not propagate.

Import Errors: If inquirer-textual or textual not installed.

try:
    from inquirer_textual import prompts
except ImportError:
    print("Please install inquirer-textual: pip install inquirer-textual")
    exit(1)

Best Practices for Error Handling

Always Check Command

result = prompts.text("Input:")
if result.command == 'select':
    # Safe to use result.value
    process(result.value)
elif result.command == 'quit':
    # User quit
    exit(0)
else:
    # Unknown command (shortcuts, etc.)
    handle_custom_command(result.command)

Validate After Prompt

result = prompts.number("Port:")
if result.command == 'select':
    try:
        port = int(result.value)
        if 1024 <= port <= 65535:
            start_server(port)
        else:
            print("Port must be 1024-65535")
    except ValueError:
        print("Invalid port number")

Handle Partial Data in Multi-Prompt

result = prompts.multi([widget1, widget2, widget3])

if result.command == 'select':
    # All completed
    v1, v2, v3 = result.value
elif result.command == 'quit':
    # Partial completion
    completed = len(result.value)
    if completed >= 2:
        # Process partial
        save_draft(result.value)

Graceful Degradation

result = prompts.text("Name (optional):")
if result.command == 'quit':
    # User quit - use default
    name = "Anonymous"
elif result.command == 'select':
    name = result.value.strip() or "Anonymous"
else:
    name = "Anonymous"

process(name)

Safe inquiry_func Pattern

def safe_flow(app):
    """Flow with comprehensive error handling."""
    try:
        r1 = app.prompt(InquirerText("First:"))
        if r1.command != 'select':
            app.stop(None)
            return

        r2 = app.prompt(InquirerText("Second:"))
        if r2.command != 'select':
            app.stop(None)
            return

        app.stop((r1.value, r2.value))

    except Exception as e:
        print(f"Error: {e}")
        app.stop(None)

app = InquirerApp()
result = app.run(inline=True, inquiry_func=safe_flow)

if result.value is None:
    print("Flow did not complete")
else:
    process(result.value)