Addresses Issue #11705 Wave 0 Discovery acceptance criteria: - ADR: Node.js-first with opt-in Bun experimental strategy - Feature parity matrix: 54 items cataloged across Ink components, hooks, and tools - Performance baseline: startup, latency, render cadence, memory benchmarks - Implementation plan: 8-week roadmap across 60 tasks in 5 waves - Terminal compatibility matrix for cross-platform validation
9.7 KiB
OpenTUI Migration Implementation Plan
Issue: #11705 Date: 2026-02-22 Duration: ~8 weeks (60 tasks across 5 waves) ADR: docs/adr/cli-opentui-runtime.md
Overview
This plan covers the full migration path from Ink to OpenTUI for the Roo CLI terminal UI. Per the ADR, the strategy is Node.js-first with opt-in experimental OpenTUI support. Each wave builds on the previous one and includes a go/no-go checkpoint.
Wave 0: Discovery (Complete)
Duration: 1 week | Status: Complete
| # | Task | Deliverable | Status |
|---|---|---|---|
| 1 | Inventory Ink UI capabilities | Parity Matrix | Done |
| 2 | Build feature parity matrix | Parity Matrix | Done |
| 3 | Validate OpenTUI runtime constraints | ADR | Done |
| 4 | Write ADR for runtime strategy | ADR | Done |
| 5 | Define MVP scope and non-goals | This document (Wave 1 scope) | Done |
| 6 | Instrument current CLI UX timings | Performance Baseline | Done |
| 7 | Define terminal compatibility matrix | Performance Baseline | Done |
Wave 1: Foundation (2 weeks)
Goal: Scaffolding, dual-renderer architecture, minimal rendering.
Go/No-Go Criteria: OpenTUI can render a static text screen in Node.js behind the --ui opentui flag.
| # | Task | Description | Estimate | Dependencies |
|---|---|---|---|---|
| 8 | Add OpenTUI dependency | Vendor or git-dep OpenTUI source into apps/cli |
2h | None |
| 9 | Create tsconfig.ui-next.json |
TypeScript config for OpenTUI/SolidJS compilation | 1h | #8 |
| 10 | Create build script for ui-next | apps/cli/scripts/build-ui-next.ts |
2h | #9 |
| 11 | Add --ui flag to CLI |
Extend commander options in run.ts |
1h | None |
| 12 | Create renderer selection logic | Switch in run.ts based on --ui flag |
2h | #11 |
| 13 | Create ui-next/main.tsx |
OpenTUI app entry point | 2h | #8, #12 |
| 14 | Create ui-next/app.tsx |
Root component with basic layout | 4h | #13 |
| 15 | Implement view-model contracts | Define interfaces for UI state consumed by both renderers | 4h | None |
| 16 | Create adapter layer | Bridge between Zustand stores and OpenTUI reactive state | 4h | #15 |
| 17 | Add error boundary with fallback | Catch OpenTUI errors, fall back to plain text output | 2h | #14 |
| 18 | Wire exit/lifecycle hooks | Process exit, SIGINT handling for OpenTUI context | 2h | #14 |
| 19 | Add smoke test for ui-next | Vitest test that OpenTUI app initializes without crash | 2h | #14 |
| 20 | Update CI to build ui-next | Add build step for OpenTUI target | 1h | #10 |
Wave 2: Core Components (2 weeks)
Goal: Port essential display components for basic chat interaction.
Go/No-Go Criteria: Can display a streaming chat message and show the header/footer.
| # | Task | Description | Estimate | Dependencies |
|---|---|---|---|---|
| 21 | Port Text styling helpers |
Color, bold, dim, italic text rendering | 2h | Wave 1 |
| 22 | Port Box layout equivalent |
Flexbox container with padding/margin/border | 4h | Wave 1 |
| 23 | Port Header component |
Status bar with mode, model, metrics | 2h | #21, #22 |
| 24 | Port HorizontalLine |
Separator component | 1h | #21 |
| 25 | Port Icon component |
Unicode icon rendering | 1h | #21 |
| 26 | Port LoadingText / Spinner |
Frame-based animation without @inkjs/ui | 4h | #21 |
| 27 | Port ProgressBar |
Visual progress indicator | 2h | #21 |
| 28 | Port ChatHistoryItem |
Message rendering with markdown/code blocks | 8h | #21, #22 |
| 29 | Port MetricsDisplay |
Token count and cost display | 2h | #21, #22 |
| 30 | Port ToastDisplay |
Toast notification component | 2h | #21, #22 |
| 31 | Port TodoDisplay |
Checklist rendering | 2h | #21, #22 |
| 32 | Port TodoChangeDisplay |
Todo diff display | 2h | #21, #22 |
| 33 | Port tool display components (7) | CommandTool, FileReadTool, FileWriteTool, etc. | 8h | #21, #22 |
| 34 | Create component test helpers | Test utilities replacing ink-testing-library |
4h | Wave 1 |
| 35 | Add component unit tests | Tests for ported components | 8h | #34 |
Wave 3: Input and Interaction (2 weeks)
Goal: Port input handling, autocomplete, and selection components.
Go/No-Go Criteria: Can type a message, use autocomplete, and submit to the agent.
| # | Task | Description | Estimate | Dependencies |
|---|---|---|---|---|
| 36 | Create input event handler | Replace Ink's useInput with OpenTUI key events |
8h | Wave 2 |
| 37 | Port MultilineTextInput |
Full cursor management and line editing | 12h | #36 |
| 38 | Port ScrollArea |
Scrollable container (without measureElement) |
12h | #22, #36 |
| 39 | Port ScrollIndicator |
Scroll position display | 2h | #38 |
| 40 | Port AutocompleteInput |
Keyboard-driven autocomplete overlay | 8h | #36, #37 |
| 41 | Port PickerSelect |
Filterable selection list | 6h | #36, #22 |
| 42 | Port autocomplete triggers (5) | File, Help, History, Mode, SlashCommand triggers | 8h | #40 |
| 43 | Port OnboardingScreen |
First-run wizard with Select replacement | 4h | #41 |
| 44 | Port useGlobalInput hook |
Global keyboard shortcut handling | 4h | #36 |
| 45 | Port useFocusManagement |
Focus toggle between scroll area and input | 4h | #36 |
| 46 | Integration test: full chat flow | End-to-end test of message input, display, scroll | 8h | #37-#45 |
Wave 4: Polish and Evaluation (2 weeks)
Goal: Performance testing, cross-platform validation, go/no-go decision.
| # | Task | Description | Estimate | Dependencies |
|---|---|---|---|---|
| 47 | Run performance benchmarks | Collect all metrics from baseline doc | 4h | Wave 3 |
| 48 | Compare startup time | Ink vs OpenTUI cold/warm start | 2h | #47 |
| 49 | Compare input latency | Ink vs OpenTUI keypress-to-render | 2h | #47 |
| 50 | Compare memory usage | Heap profiles under load | 2h | #47 |
| 51 | Compare render cadence | FPS during streaming | 2h | #47 |
| 52 | Test on iTerm2 | macOS validation | 2h | Wave 3 |
| 53 | Test on Terminal.app | macOS fallback validation | 2h | Wave 3 |
| 54 | Test on GNOME Terminal | Linux validation | 2h | Wave 3 |
| 55 | Test on Windows Terminal | Windows validation | 2h | Wave 3 |
| 56 | Test on tmux | Multiplexer validation | 2h | Wave 3 |
| 57 | Test on VS Code terminal | IDE terminal validation | 1h | Wave 3 |
| 58 | Document findings | Update ADR with results | 4h | #47-#57 |
| 59 | Write go/no-go recommendation | Based on all collected data | 2h | #58 |
| 60 | Present to team | Summary of findings and recommendation | 2h | #59 |
MVP Scope (Wave 1)
In Scope
- Static text rendering (header, messages, status)
--ui opentuiflag for opt-in- Error boundary with plain-text fallback
- Basic layout (vertical stacking, padding)
- Process lifecycle management
Out of Scope (Deferred)
- Input handling (Wave 3)
- Autocomplete (Wave 3)
- Scroll virtualization (Wave 3)
- Mouse support
- Custom themes
- Animation beyond spinner
- Accessibility features
- Plugin/extension API
Non-Goals
These items are explicitly not part of this migration effort:
- Changing the non-TUI modes --
--printand--output-format stream-jsonare unaffected - Modifying the agent/core -- Only
apps/cli/src/ui*andrun.tsare in scope - Removing Ink -- Ink remains the default renderer; OpenTUI is experimental
- Requiring Bun -- Node.js compatibility is mandatory per the ADR
- Changing the build system -- tsup remains the bundler; ui-next gets its own tsconfig
Risk Register
| # | Risk | Probability | Impact | Mitigation | Owner |
|---|---|---|---|---|---|
| R1 | OpenTUI cannot run on Node.js | Medium | Critical | Validate in Wave 1 task #13; abort if fails | CLI team |
| R2 | ScrollArea cannot be ported without measureElement | Medium | High | Implement custom measurement using ANSI escape sequences | CLI team |
| R3 | Input latency regression | Low | High | Benchmark in Wave 4; revert to Ink if >2x slower | CLI team |
| R4 | Test coverage drops significantly | Medium | Medium | Build test helpers in Wave 2 (#34); maintain parity | CLI team |
| R5 | Cross-terminal rendering inconsistencies | Medium | Medium | Test matrix in Wave 4; document known issues | CLI team |
| R6 | OpenTUI API breaks during migration | Low | Medium | Pin to specific commit/version; vendor if needed | CLI team |
Effort Summary
| Wave | Duration | Tasks | Estimated Hours |
|---|---|---|---|
| Wave 0 (Discovery) | 1 week | 7 | 20h |
| Wave 1 (Foundation) | 2 weeks | 13 | 29h |
| Wave 2 (Core Components) | 2 weeks | 15 | 53h |
| Wave 3 (Input/Interaction) | 2 weeks | 11 | 76h |
| Wave 4 (Polish/Evaluation) | 2 weeks | 14 | 32h |
| Total | ~8 weeks | 60 | ~210h |
Decision Points
- After Wave 1: Can OpenTUI render anything on Node.js? If no, abort.
- After Wave 2: Is component porting feasible at acceptable quality? If not, abort.
- After Wave 3: Does the full interaction model work? If critical gaps exist, pause.
- After Wave 4: Performance and compatibility results drive final go/no-go.