diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 170b604..71aa280 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,14 +3,9 @@ name: CI on: pull_request: branches: [main] - paths: - - "*.js" - - "src/**/*.js" - - "tests/**/*.js" - - "package.json" - - "package-lock.json" - - ".github/workflows/*.yml" - - ".github/dependabot.yml" + paths-ignore: + - "**/*.md" + - "openspec/**" jobs: check: diff --git a/openspec/changes/refactor-tui-opentui/.openspec.yaml b/openspec/changes/refactor-tui-opentui/.openspec.yaml new file mode 100644 index 0000000..c53ef21 --- /dev/null +++ b/openspec/changes/refactor-tui-opentui/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-05 diff --git a/openspec/changes/refactor-tui-opentui/design.md b/openspec/changes/refactor-tui-opentui/design.md new file mode 100644 index 0000000..6d46265 --- /dev/null +++ b/openspec/changes/refactor-tui-opentui/design.md @@ -0,0 +1,91 @@ +## Context + +The TUI at `src/tui/` (~1,400 lines, 16 files) is built on Ink, a React-to-terminal renderer. It uses four separate npm packages to provide scrolling (`ink-scroll-view`), markdown (`marked` + `marked-terminal`), and custom cursor animation. OpenTUI (`@opentui/core` + `@opentui/react`) is a Zig-native renderer with a React reconciler that provides all these capabilities in a single package. + +OpenTUI powers OpenCode in production and is actively developed at anomalyco/opentui (11.7k stars). It uses the Yoga layout engine (same as React Native) for flexbox layout, and has native components for scrollbox, input, markdown, text, and box. + +The `tui-interface` spec defines behavioral requirements (chat mode, batch mode, keyboard nav, commands, banner) that must be preserved. The `tui-scroll-view`, `input-cursor`, and `markdown-rendering` specs describe current Ink-specific implementations that are being replaced. + +## Goals / Non-Goals + +**Goals:** +- Replace Ink + `ink-scroll-view` + `marked` + `marked-terminal` with `@opentui/core` + `@opentui/react` +- Preserve all behavioral requirements from `tui-interface` (chat, batch, pipeline, keyboard nav, commands, banner) +- Eliminate ~160 lines of hand-rolled scroll/auto-scroll/resize logic +- Eliminate four npm dependencies, replace with two +- Keep pure utility modules (`panels.js`, `hooks.js`, `messages.js`, `commandParser.js`) untouched + +**Non-Goals:** +- Replacing `posix` dependency (not a TUI concern in current code) +- Modifying config schema beyond removing `tui.blinkTimeout` +- Modifying session factory, memory, scheduler, or other non-TUI subsystems +- Adding new UI features or panels + +## Decisions + +### Decision: Use OpenTUI React reconciler, not raw renderables +OpenTUI provides two APIs: the imperative renderable API (`new BoxRenderable(...)`, manual `renderer.root.add()`) and the React JSX reconciler (`@opentui/react`). We use the React reconciler because: +- The codebase is already React-based — only JSX element names, props, and hooks change +- State management (`useState`, `useEffect`, `useRef`, `React.memo`) stays identical +- Pure utility modules export unchanged +- Significantly less refactoring cost than imperative rewrite + +### Decision: Replace JSX, not refactor to functional components +Current code uses `React.createElement()` everywhere (no JSX transpilation). The refactor uses: +- JSX notation (``, ``) instead of `React.createElement()` — this is the idiomatic OpenTUI React pattern +- This requires the app to be run with Bun (which has built-in JSX support) +- The OpenTUI React setup (`jsxImportSource: "@opentui/react"`) is handled by Bun's runtime + +### Decision: Replace scroll logic with native sticky scroll +Current code in `conversationPanel.js` has ~100 lines of scroll utilities: `handleScrollInput`, `handleResize`, `handleAutoScroll`, `executeScrollInput`, `executeResize`, `executeAutoScroll`. These are all replaced by OpenTUI's ``. The sticky scroll behavior matches the current "auto-scroll on new message" functionality, and when the user manually scrolls up, sticky scroll pauses (same UX as current scroll-view). + +### Decision: Remove blinking cursor animation, keep static display +The `inputPanel.js` currently renders a blinking cursor via `getBlinkState()` and `renderBlink()` driven by an animation counter. The input panel is display-only (all input handling is in `app.js` via `useInput`). We simplify to a static text display with cursor character. This is a minor visual change — the cursor is always visible at the text position. + +### Decision: Replace markdown parsing with native component +Current `markdownText.js` uses `marked.parse()` + `marked-terminal` to convert markdown to ANSI escape sequences, then renders in a `Text` component. We replace this with OpenTUI's native `` component. The native component supports Tree-sitter syntax highlighting for code blocks, streaming mode, and markdown table rendering — all improvements over the current implementation. + +### Decision: Key bindings map to OpenTUI key.event model +Ink's `useInput` passes `(input, key)` where key has boolean props like `key.upArrow`. OpenTUI's `useKeyboard` passes a `KeyEvent` with `key.name` (canonical string like `"up"`, `"down"`, `"return"`). Mapping: +- `key.upArrow` → `key.name === "up"` +- `key.downArrow` → `key.name === "down"` +- `key.return` → `key.name === "return"` +- `key.backspace` → `key.name === "backspace"` +- `key.pageUp` → `key.name === "pageup"` +- `key.pageDown` → `key.name === "pagedown"` + +## Risks / Trade-offs + +### Risk: OpenTUI requires Bun at runtime +**Impact:** The start script changes from `node index.js --mode interactive` to `bun index.js --mode interactive`. +**Mitigation:** The project's tooling (lint, test, coverage via `oxlint`, `oxfmt`, `node --test`) stays on Node.js. Only the TUI entry point needs Bun. `@opentui/react` is Bun-exclusive today, but Node/Deno support is "in-progress." If Node support lands later, the Bun dependency can be relaxed. + +### Risk: JSX lowercase convention changes +**Impact:** All TUI components must use `` / `` / `` instead of `` / ``. Color strings change from named ( `"green"` ) to hex ( `"#00FF00"` ). +**Mitigation:** This is a straightforward find-and-replace across 12 files. No semantic changes. + +### Risk: Native scroll behavior may differ from custom logic +**Impact:** OpenTUI's sticky scroll behavior auto-scrolls on new content. The custom logic had additional scroll overflow detection (checking `contentHeight > viewportHeight` during streaming). With sticky scroll, this is built-in and simpler. +**Mitigation:** The sticky scroll spec covers this explicitly — sticky scroll pauses when user scrolls up, matching current behavior. + +### Risk: Markdown rendering visual differences +**Impact:** `marked` + `marked-terminal` produces different ANSI output than OpenTUI's native ``. Code block highlighting, table rendering, and heading styles may look different. +**Mitigation:** The spec's behavioral requirements (bold renders bold, lists render with bullets, etc.) are preserved. Visual style differences are within acceptable range. If any specific styling is important, it can be tuned via OpenTUI's `SyntaxStyle` API. + +## Migration Plan + +1. Install Bun (`corepack prepare bun@latest --activate` or via official installer) +2. Add `@opentui/core` and `@opentui/react` to `package.json` +3. Remove `ink`, `ink-scroll-view`, `marked`, `marked-terminal` +4. Update entry point (`index.js`) — replace `` with `createCliRenderer()` / `createRoot()`, add Bun availability check +5. Update all TUI component files (`app.js`, `conversationPanel.js`, `statusBar.js`, `banner.js`, `skillsPanel.js`, `memoryPanel.js`, `settingsPanel.js`, `onboardingPanel.js`, `inputPanel.js`, `markdownText.js`) +6. Remove `tui.blinkTimeout` from config schema (`src/config/schemas.js`) if present +7. Update tests (`tests/unit/tui.test.js`) +8. Run `npm run fix` for lint/format compliance — note: lint uses Node, format uses oxfmt which is Node-compatible +9. Run tests for coverage verification + +## Resolved Decisions + +1. **Config schema:** `tui.blinkTimeout` MUST be explicitly removed from the config schema. The `tui.cursorChar` field remains. +2. **Bun installation:** `index.js` MUST check for Bun availability on startup. If Bun is not installed (e.g., `process.execPath` does not contain `bun`), log a clear error and exit. The project will install Bun via `corepack prepare` or a `.tool-versions` / ` bun-version` file for reproducibility. +3. **Streaming markdown behavior:** Keep the current `setMessages` pattern for streaming. The message object stores `content` with the accumulated text (including a cursor placeholder character). The `` component is rendered from `msg.content` on each render cycle. No special streaming mode needed — OpenTUI's React reconciler will re-render the `` with updated content on each React state update, same as the current `marked` approach but without the intermediate ANSI parsing step. diff --git a/openspec/changes/refactor-tui-opentui/proposal.md b/openspec/changes/refactor-tui-opentui/proposal.md new file mode 100644 index 0000000..ab56b6a --- /dev/null +++ b/openspec/changes/refactor-tui-opentui/proposal.md @@ -0,0 +1,39 @@ +## Why + +The TUI runs on Ink (a React-to-terminal renderer) plus separate packages for scrolling (`ink-scroll-view`), markdown (`marked` + `marked-terminal`), and custom cursor blink logic. OpenTUI (`@opentui/core` + `@opentui/react`) provides these same capabilities natively through its Zig renderer core, eliminating four npm dependencies and ~160 lines of hand-rolled logic. + +## What Changes + +- Replace `ink` + `ink-scroll-view` with `@opentui/core` + `@opentui/react` +- JSX elements change from PascalCase (``, ``) to lowercase (``, ``) with hex foreground colors +- Replace `marked` + `marked-terminal` with OpenTUI's native `` component +- Replace hand-rolled scroll auto-scroll/resize/remeasure logic with OpenTUI's `stickyScroll` + `stickyStart` on `ScrollBox` +- Replace hand-rolled blinking cursor display with OpenTUI's built-in `` cursor, or a simple static `` (display-only behavior unchanged) +- Replace Ink hooks (`useInput`, `useWindowSize`, `useStdout`) with OpenTUI React hooks (`useKeyboard`, `useTerminalDimensions`, `useOnResize`) +- Update entry point to use `createCliRenderer()` / `createRoot()` instead of Ink's `` +- Update key bindings: `key.upArrow` → `key.name === "up"`, etc. + +## Capabilities + +### New Capabilities + +- `tui-opentui`: Runtime migration from Ink to OpenTUI (Zig-native terminal renderer with React reconciler) + +### Modified Capabilities + +- `tui-scroll-view`: Replaces `ink-scroll-view` with native OpenTUI `ScrollBox` + `stickyScroll`. **BREAKING** — the spec's requirement to use `ink-scroll-view` is replaced. +- `input-cursor`: Cursor display remains (visible at end of input text) but blinking animation is removed in favor of the display-only input panel pattern. **BREAKING** — the `blinkTimeout` config field is no longer applicable. +- `markdown-rendering`: Replaces `marked` + `marked-terminal` with OpenTUI's native `` component. The rendered result is the same — requirements are preserved. **BREAKING** — implementation changes from a custom parser to a native component. +- `tui-interface`: No requirement changes — all behavioral requirements (chat mode, batch mode, keyboard nav, command entry, banner) remain identical. Only the underlying renderer changes. + +## Impact + +**Code:** 12 of 16 `src/tui/` files. 4 utility files (`panels.js`, `hooks.js`, `messages.js`, `commandParser.js`) unchanged. + +**Dependencies:** Remove `ink`, `ink-scroll-view`, `marked`, `marked-terminal`. Add `@opentui/core`, `@opentui/react`. + +**Entry point:** `index.js` — `` wrapper replaced by `createCliRenderer()` / `createRoot()`. + +**Tests:** `tests/unit/tui.test.js` — rendering helpers need updating for OpenTUI React's JSX model. Pure utility tests unaffected. + +**Config:** `tui.blinkTimeout` config field is no longer relevant. `tui.cursorChar` remains supported. diff --git a/openspec/changes/refactor-tui-opentui/specs/input-cursor/spec.md b/openspec/changes/refactor-tui-opentui/specs/input-cursor/spec.md new file mode 100644 index 0000000..8048a9a --- /dev/null +++ b/openspec/changes/refactor-tui-opentui/specs/input-cursor/spec.md @@ -0,0 +1,37 @@ +## REMOVED Requirements + +### Requirement: Blinking Input Cursor +**Reason**: Replaced by display-only input panel; cursor is handled by OpenTUI's native `` or a static display +**Migration**: The animated blink interval is removed; the input panel simply displays text with a fixed cursor character + +### Requirement: Configurable Blink Interval +**Reason**: Blink animation is removed as a display concern +**Migration**: The `tui.blinkTimeout` config field is no longer referenced by the input panel + +### Requirement: Configurable Cursor Character +**Reason**: Cursor character display is kept but simplifies to a single configurable string +**Migration**: `tui.cursorChar` remains supported and continues to default to `█` (U+2588 FULL BLOCK) + +## ADDED Requirements + +### Requirement: Input panel displays text with static cursor display +The input panel SHALL display the user's current input text followed by a cursor character, without animation. The cursor character is configured via `tui.cursorChar`. + +#### Scenario: Cursor character appears after input text +- **WHEN** the user types text in the input panel +- **THEN** the cursor character appears immediately after the last character + +#### Scenario: Cursor is visible in empty input +- **WHEN** the input panel is displayed with no text +- **THEN** the cursor character still appears (indicating the active input position) + +### Requirement: Config schema removes blinkTimeout +The `tui` configuration section SHALL NOT include a `blinkTimeout` field. The existing `tui.cursorChar` field remains with its default value of `█` (U+2588 FULL BLOCK). + +#### Scenario: blinkTimeout is removed from schema +- **WHEN** the config schema at `src/config/schemas.js` is inspected +- **THEN** no `blinkTimeout` field exists in the `tui` schema definition + +#### Scenario: cursorChar remains with default +- **WHEN** the config schema is inspected +- **THEN** `tui.cursorChar` exists with default `"\u2588"` diff --git a/openspec/changes/refactor-tui-opentui/specs/markdown-rendering/spec.md b/openspec/changes/refactor-tui-opentui/specs/markdown-rendering/spec.md new file mode 100644 index 0000000..89e8901 --- /dev/null +++ b/openspec/changes/refactor-tui-opentui/specs/markdown-rendering/spec.md @@ -0,0 +1,53 @@ +## REMOVED Requirements + +### Requirement: Markdown rendering in conversation panel +**Reason**: Implementation replaced by OpenTUI native `` component (parser-level change, not behavioral) +**Migration**: `marked` + `marked-terminal` npm dependencies are removed + +## ADDED Requirements + +### Requirement: Markdown rendering uses native OpenTUI component +The system SHALL render markdown-formatted message content using the OpenTUI `` component, applying visual formatting for supported syntax elements including bold, italic, inline code, code blocks, headings, lists, and links. + +#### Scenario: Bold text renders with emphasis +- **WHEN** a user or assistant message contains `**bold**` or `__bold__` markdown +- **THEN** the terminal displays the text as bold in the conversation panel + +#### Scenario: Italic text renders with emphasis +- **WHEN** a message contains `*italic*` or `_italic*` markdown +- **THEN** the terminal displays the text as italic in the conversation panel + +#### Scenario: Inline code renders with distinct styling +- **WHEN** a message contains backtick-enclosed inline code `` `code` `` +- **THEN** the terminal displays the inline code with a visual distinction from regular text + +#### Scenario: Fenced code blocks render as multiline blocks +- **WHEN** a message contains a fenced code block delimited by `` ``` `` +- **THEN** the terminal displays the block as a visually separated multiline region with syntax highlighting + +#### Scenario: Unordered lists render with bullet markers +- **WHEN** a message contains list items prefixed with `- ` or `* ` +- **THEN** the terminal renders each item with a bullet marker + +#### Scenario: Headings render with emphasis +- **WHEN** a message contains heading text prefixed with `# ` +- **THEN** the terminal displays the heading with a visual distinction from body text + +#### Scenario: Plain text messages remain unchanged +- **WHEN** a message contains no markdown formatting characters +- **THEN** the terminal displays the message exactly as plain text with no visual changes + +#### Scenario: Unrecognized markdown degrades gracefully +- **WHEN** a message contains markdown syntax the terminal cannot render +- **THEN** the system displays the raw content without errors or crashes + +### Requirement: Markdown rendering for both user and assistant messages +The system SHALL render markdown formatting bidirectionally — message content from both `user` and `assistant` roles receives markdown rendering via the native component. + +#### Scenario: User-sent markdown renders with formatting +- **WHEN** a user types a message containing markdown syntax and sends it +- **THEN** the user's own message displays with proper markdown formatting + +#### Scenario: Assistant markdown response renders with formatting +- **WHEN** the assistant responds with a message containing markdown syntax +- **THEN** the assistant's response displays with proper markdown formatting diff --git a/openspec/changes/refactor-tui-opentui/specs/tui-opentui/spec.md b/openspec/changes/refactor-tui-opentui/specs/tui-opentui/spec.md new file mode 100644 index 0000000..7547adb --- /dev/null +++ b/openspec/changes/refactor-tui-opentui/specs/tui-opentui/spec.md @@ -0,0 +1,37 @@ +## ADDED Requirements + +### Requirement: TUI Runtime uses OpenTUI renderer +The TUI SHALL use `@opentui/core` and `@opentui/react` as the rendering engine instead of `ink` and `ink-scroll-view`. + +#### Scenario: Renderer initialization +- **WHEN** the app starts in interactive mode (`--mode interactive`) +- **THEN** the entry point calls `createCliRenderer({ exitOnCtrlC: true })` and renders via `createRoot(renderer).render()` + +#### Scenario: Renderer cleanup on shutdown +- **WHEN** the app exits (Ctrl+C, SIGTERM, or programmatic exit) +- **THEN** `renderer.destroy()` is called in a `finally` block, restoring the terminal and freeing native resources + +#### Scenario: Entry point uses bun +- **WHEN** the start script is invoked +- **THEN** it runs via `bun index.js` (OpenTUI requires Bun at runtime) + +### Requirement: OpenTUI React JSX conventions are used +All TUI components SHALL use OpenTUI React's lowercase JSX elements (``, ``, ``, ``, ``) with hex color values and style objects for attributes. + +#### Scenario: No PascalCase JSX primitives +- **WHEN** TUI component files are inspected +- **THEN** no ``, ``, or other PascalCase elements from `ink` are present + +#### Scenario: Colors use hex values or OpenTUI color names +- **WHEN** colors are applied to elements +- **THEN** hex values (e.g., `fg="#00FF00"`) or OpenTUI color names are used instead of Ink color strings + +## REMOVED Requirements + +### Requirement: Ink Application wrapper +**Reason**: Replaced by OpenTUI renderer +**Migration**: Entry point now uses `createCliRenderer()` / `createRoot()` directly + +### Requirement: Ink hooks (useInput, useWindowSize, useStdout) +**Reason**: Replaced by OpenTUI React hooks +**Migration**: `useInput` → `useKeyboard`, `useWindowSize` → `useTerminalDimensions`, `stdout.on("resize")` → `useOnResize` diff --git a/openspec/changes/refactor-tui-opentui/specs/tui-scroll-view/spec.md b/openspec/changes/refactor-tui-opentui/specs/tui-scroll-view/spec.md new file mode 100644 index 0000000..44f7387 --- /dev/null +++ b/openspec/changes/refactor-tui-opentui/specs/tui-scroll-view/spec.md @@ -0,0 +1,42 @@ +## REMOVED Requirements + +### Requirement: Conversation panel uses ScrollView for rendering messages +**Reason**: Replaced by OpenTUI native ScrollBox +**Migration**: The `ScrollView` from `ink-scroll-view` is replaced by the built-in `` component with `stickyScroll={true}` and `stickyStart="bottom"` + +### Requirement: Conversation panel handles keyboard scroll input +**Reason**: ScrollBox handles keyboard navigation natively +**Migration**: Arrow keys, page-up, page-down, home, and end are handled automatically by ScrollBox without custom `useInput` scroll translation + +### Requirement: Conversation panel handles terminal resize +**Reason**: OpenTUI's Yoga layout engine handles resize automatically +**Migration**: The manual `stdout.on("resize")` listener and `scrollRef.remeasure()` calls are removed + +### Requirement: app.js no longer manages scroll state +**Reason**: Unchanged — was already satisfied in prior refactoring +**Migration**: No behavior change required + +### Requirement: messages.js no longer exports scroll virtualization functions +**Reason**: Unchanged — spec already satisfied from prior refactoring +**Migration**: No behavior change required + +## ADDED Requirements + +### Requirement: Conversation panel uses ScrollBox with sticky scroll +The ConversationPanel SHALL render messages inside an OpenTUI `` component configured with `stickyScroll={true}` and `stickyStart="bottom"`. + +#### Scenario: ScrollBox wraps message list +- **WHEN** the ConversationPanel renders messages +- **THEN** a `` component from `@opentui/core` is the container for all message elements + +#### Scenario: Messages receive unique keys +- **WHEN** the scrollbox renders its children +- **THEN** each message element has a unique `key` prop + +#### Scenario: Auto-scroll to new messages +- **WHEN** a new message is added to the conversation +- **THEN** sticky scroll keeps the view at the bottom automatically + +#### Scenario: User can scroll up to read history +- **WHEN** the user scrolls up in the scrollbox +- **THEN** sticky scroll pauses (user-controlled scroll is not overridden) until the user scrolls back to the bottom diff --git a/openspec/changes/refactor-tui-opentui/tasks.md b/openspec/changes/refactor-tui-opentui/tasks.md new file mode 100644 index 0000000..64c475b --- /dev/null +++ b/openspec/changes/refactor-tui-opentui/tasks.md @@ -0,0 +1,70 @@ +## 1. Setup and Dependency Changes + +- [ ] 1.1 Install Bun (`corepack prepare bun@latest --activate` or via official installer) +- [ ] 1.2 Add `@opentui/core` and `@opentui/react` to package.json dependencies +- [ ] 1.3 Remove `ink`, `ink-scroll-view`, `marked`, `marked-terminal` from package.json dependencies +- [ ] 1.4 Install new dependencies (`bun install`) + +## 2. Entry Point + +- [ ] 2.1 Replace Ink's `` wrapper with `createCliRenderer()` / `createRoot()` in `index.js` +- [ ] 2.2 Add `try/finally { renderer.destroy() }` for cleanup +- [ ] 2.3 Update start script from `node index.js --mode interactive` to `bun index.js --mode interactive` +- [ ] 2.4 Update `app.js` imports: replace `React`, `Box`, `Text`, `useWindowSize`, `useInput` from `ink` with `@opentui/react` imports +- [ ] 2.5 Add Bun availability check at top of `index.js`: verify `process.execPath` contains `bun`, log clear error and exit with code 1 if not found + +## 3. App Component (app.js) + +- [ ] 3.1 Replace all `React.createElement(Box, ...)` with `` JSX and `React.createElement(Text, ...)` with `` JSX +- [ ] 3.2 Replace Ink hooks: `useInput` → `useKeyboard`, `useWindowSize` → `useTerminalDimensions` +- [ ] 3.3 Map key bindings: `key.upArrow` → `key.name === "up"`, `key.downArrow` → `key.name === "down"`, etc. +- [ ] 3.4 Map color props: `"green"` → `#00FF00`, `"cyan"` → `#00FFFF`, `"yellow"` → `#FFFF00`, `"gray"` → `#888888`, `"red"` → `#FF0000` +- [ ] 3.5 Replace `dim: true` with `style={{ dim: true }}` or equivalent +- [ ] 3.6 Map `borderStyle: "round"` → `borderStyle="rounded"` + +## 4. Conversation Panel (conversationPanel.js) + +- [ ] 4.1 Replace `ink-scroll-view` ScrollView with OpenTUI `` +- [ ] 4.2 Remove scroll utility functions: `handleScrollInput`, `handleResize`, `handleAutoScroll`, `executeScrollInput`, `executeResize`, `executeAutoScroll` (~70 lines) +- [ ] 4.3 Remove `useInput` scroll handler — ScrollBox handles keyboard navigation natively +- [ ] 4.4 Remove stdout resize listener and `scrollRef.remeasure()` call +- [ ] 4.5 Update `getRoleColors` to return hex values instead of color names +- [ ] 4.6 Update `getBubbleStyle` to return hex values instead of color names +- [ ] 4.7 Convert all `React.createElement` calls to JSX (``, ``, ``) + +## 5. Input Panel (inputPanel.js) + +- [ ] 5.1 Remove `Blink` component and `getBlinkState` / `renderBlink` functions +- [ ] 5.2 Simplify `InputPanel` to display text + static cursor character using `` components +- [ ] 5.3 Convert remaining `React.createElement` calls to JSX + +## 6. Markdown Rendering (markdownText.js) + +- [ ] 6.1 Remove `marked` and `marked-terminal` imports and `parseMarkdown` function +- [ ] 6.2 Replace custom markdown rendering with OpenTUI's `` component +- [ ] 6.3 Handle streaming: during chat streaming, feed accumulated content into `` component + +## 7. Remaining TUI Components + +- [ ] 7.1 `statusBar.js` — JSX lowercase, hex colors, `style={{ dim: true }}` +- [ ] 7.2 `banner.js` — JSX lowercase, replace `useInput` with `useKeyboard`, map key names +- [ ] 7.3 `skillsPanel.js` — JSX lowercase, `useKeyboard` with `key.name === "up"/"down"` +- [ ] 7.4 `memoryPanel.js` — JSX lowercase, `useKeyboard` with `key.name` mapping +- [ ] 7.5 `settingsPanel.js` — JSX lowercase, `useKeyboard` with `key.name === "return"` +- [ ] 7.6 `onboardingPanel.js` — JSX lowercase, prop mapping (`width: "60"` → `width={60}`) + +## 8. Test Updates + +- [ ] 8.1 Update `tests/unit/tui.test.js` — replace Ink test helpers with OpenTUI React equivalents +- [ ] 8.2 Verify pure utility tests still pass (`commandParser`, `messages`, `panels`, `hooks` — they import no TUI components that changed) +- [ ] 8.3 Run `npm run test` to verify all tests pass +- [ ] 8.4 Run `npm run coverage` to verify 100% coverage + +## 9. Config and Housekeeping + +- [ ] 9.1 Remove `tui.blinkTimeout` from config schema at `src/config/schemas.js` if present (grep in design found no current usage — verify schema is clean) +- [ ] 9.2 Keep `tui.cursorChar` in config schema and default value at `src/config/schemas.js:107` — verify unchanged +- [ ] 9.3 Ensure `package.json` dependencies section is clean (no stale references) +- [ ] 9.4 Run `npm run fix` for lint/format compliance +- [ ] 9.5 Run `npm run lint` to verify no warnings +- [ ] 9.6 Verify `src/tui/index.js` exports are still correct for any downstream usage