Skip to content

fix: refactor custom cursor to use Ink's useCursor hook #265

@avoidwork

Description

@avoidwork

Summary

The InputPanel component in src/tui/inputPanel.js uses a custom block character cursor (\u2588) appended to the input text, which has significant issues. It should be refactored to use Ink's built-in useCursor hook for proper terminal cursor control.

Environment

  • OS: Unknown — user to confirm
  • Node.js: Unknown — user to confirm
  • madz version: Unknown — user to confirm
  • LLM provider: N/A (TUI rendering issue)

Reproduction

  1. Start the madz TUI
  2. Observe the input cursor at the bottom of the screen, below the status bar
  3. The cursor is rendered as a block character () appended to the input text

Expected Behavior

The cursor should be controlled by Ink's useCursor hook, which sets the actual terminal cursor position after each render. This provides:

  • Proper terminal cursor behavior (blinking, positioning)
  • IME (Input Method Editor) support — the composing character is displayed at the cursor location
  • No visual artifacts from a block character in the text flow

Actual Behavior

The cursor is a static block character (\u2588) rendered as part of the InputPanel component's output. This approach:

  • Does not control the actual terminal cursor position
  • Lacks IME support
  • Can cause visual glitches or misalignment
  • Is not the idiomatic Ink pattern for cursor management

Additional Context

Ink's useCursor hook provides setCursorPosition({ x, y }) to position the cursor relative to the Ink output after each render. The cursor position should be calculated based on the input text length and passed to setCursorPosition. Passing undefined to setCursorPosition hides the cursor.

Reference: https://github.com/vadimdemedes/ink#usecursor


Audit Findings

  • File: src/tui/inputPanel.js — Current cursor is a block character (\u2588) rendered as part of the Blink component output. The InputPanel delegates to Blink which appends the cursor character after the text. No cursor positioning logic exists.
  • File: src/tui/app.js:1228InputPanel is rendered in the bottom row of the layout, after ConversationPanel and StatusBar. The cursor position (x, y) must be calculated relative to the Ink output root.
  • File: src/tui/statusBar.js — The status bar is a single-row component rendered immediately above the input. The cursor Y position will be totalRows - 1 (last row).
  • No existing useCursor usage — A grep for useCursor and setCursorPosition returned zero results. This is a greenfield adoption of the hook.
  • Integration point: The InputPanel component needs to call useCursor() and compute x = stringWidth(prompt + inputText) on each render. The y position is the last row of the Ink output.
  • Dependency: The string-width package may need to be installed if not already a dependency, as Ink docs recommend it for wide character support (CJK, emoji).

Metadata

Metadata

Assignees

No one assigned

    Labels

    approvedAn identifier for Madz to take action.bugSomething isn't workingin progress

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions