Skip to content

Compile ink (React-based TUI framework) end-to-end via perry.compilePackages #348

@proggeramlug

Description

@proggeramlug

Tracking issue for landing ink (https://github.com/vadimdemedes/ink) as a perry.compilePackages target. Surfaced in discussion #333 (TUI/CLI support) — the consensus there was that for full-screen TUIs, building a Perry-native ncurses-style framework is a lot of surface for a niche audience. ink is the modern, actively-maintained, React-based replacement for blessed / react-blessed and is what most new TS TUIs in 2025–2026 are built on (gh-copilot, claude-code, opencommit, etc.). Wiring it as a compilePackages target gives Perry users a maintained TUI library without reinventing the wheel.

Depends on

Scope

End-to-end means: a user does npm install ink react, adds \"compilePackages\": [\"ink\", \"react\"] to package.json, writes a standard ink program, runs perry compile app.tsx -o app && ./app and gets an interactive TUI binary.

The unknowns split into three buckets:

  1. React itself — Perry already ships perry-react (github.com/PerryTS/react) as a native-widget React (compiles JSX to AppKit / UIKit / Android widgets), which is not the same as ink's use case. Ink uses upstream react + react-reconciler + a custom renderer that emits ANSI to the terminal. Open question whether perry-react's React infra can be reused for ink's reconciler-as-consumer pattern, or whether DOM-style React needs to compile via compilePackages independently.
  2. Yoga layout — ink uses yoga-layout for flexbox layout in text. Yoga is traditionally a C++ lib; current yoga-layout has both a native-binding fork and a pure-JS fork. Either path needs evaluation: link libyoga natively (cross-compile story for every Perry target) vs. compile the JS port via compilePackages.
  3. ANSI utility depschalk, cli-cursor, cli-truncate, slice-ansi, string-width, strip-ansi, wrap-ansi, ansi-escapes, etc. Mostly thin string-manipulation wrappers; the gap (if any) is likely individual TS-subset features each one trips over, which is the standard compilePackages shake-out.

Acceptance

  • A 30-line ink program (counter component with keyboard input — increment on +, decrement on -, quit on q) compiles via perry compile, produces a single-file native binary, runs interactively, and matches ink running under node --experimental-strip-types byte-for-byte (modulo terminal cursor positioning timing).
  • The ink "todo list" example (https://github.com/vadimdemedes/ink#examples) compiles and runs end-to-end.
  • Whatever shake-out fixes are needed in perry-codegen / perry-stdlib to make the dependency tree compile cleanly, land them under this issue's umbrella.

Out of scope (separate follow-ups)

  • inquirer / prompts / other prompt-style libs — those are simpler (line-buffered only) and TUI gap: readline + tty.setRawMode + raw-mode stdin reader #347's Phase 1 unblocks them directly without ink.
  • blessed / react-blessed — older, flakier, not the recommended path. Documenting why we picked ink over blessed is enough.
  • A Perry-native TUI framework written from scratch — would be a bigger lift than wiring ink and competes with a maintained library; skip unless ink turns out to be unworkable.

Discussion: #333. Primitives: #347.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions