Skip to content

Commit 77ef0f3

Browse files
feat(cli): migrate Markdown rendering to remark/unified; remove legacy renderer and update dependencies
This replaces the old markdown renderer (marked/marked-terminal) with a remark/unified-based pipeline. It updates CLI dependencies (remark-parse/unified) and adjusts dev workflow to Bun-based watch mode. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent ebd2404 commit 77ef0f3

File tree

7 files changed

+556
-4
lines changed

7 files changed

+556
-4
lines changed

cli/bun.lock

Lines changed: 160 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/knowledge.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# CLI Package Knowledge
2+
3+
## OpenTUI Text Rendering Constraints
4+
5+
**CRITICAL**: OpenTUI has strict requirements for text rendering that must be followed:
6+
7+
### Text Styling Components Must Be Wrapped in `<text>`
8+
9+
All text styling components (`<strong>`, `<em>`, `<span>`, etc.) **MUST** be nested inside a `<text>` component. They cannot be returned directly from render functions.
10+
11+
**INCORRECT** ❌:
12+
```tsx
13+
// This will cause a black screen!
14+
function renderMarkdown(content: string) {
15+
return (
16+
<>
17+
<strong>Bold text</strong>
18+
<em>Italic text</em>
19+
</>
20+
)
21+
}
22+
```
23+
24+
**CORRECT** ✅:
25+
```tsx
26+
// All styling must be inside <text>
27+
function renderMarkdown(content: string) {
28+
return (
29+
<text wrap>
30+
<strong>Bold text</strong>
31+
<em>Italic text</em>
32+
</text>
33+
)
34+
}
35+
```
36+
37+
### Why This Matters
38+
39+
- Returning styling components without `<text>` wrapper causes the entire app to render as a black screen
40+
- No error messages are shown - the app just fails silently
41+
- This applies to ALL text styling: `<strong>`, `<em>`, `<span>`, `<u>`, etc.
42+
43+
### Available OpenTUI Components
44+
45+
**Core Components**:
46+
- `<text>` - The fundamental component for displaying all text content
47+
- `<box>` - Container for layout and grouping
48+
- `<input>` - Text input field
49+
- `<select>` - Selection dropdowns
50+
- `<scrollbox>` - Scrollable container
51+
- `<tab-select>` - Tab-based navigation
52+
- `<ascii-font>` - ASCII art text rendering
53+
54+
**Text Modifiers** (must be inside `<text>`):
55+
- `<span>` - Generic inline styling
56+
- `<strong>` and `<b>` - Bold text
57+
- `<em>` and `<i>` - Italic text
58+
- `<u>` - Underlined text
59+
- `<br>` - Line break
60+
61+
### Markdown Rendering Implementation
62+
63+
**SUCCESS**: Rich markdown rendering has been implemented using `unified` + `remark-parse` with OpenTUI components.
64+
65+
**Key Insight**: OpenTUI does **not support nested `<text>` components**. Since `chat.tsx` already wraps content in a `<text>` component, the markdown renderer must return **inline JSX elements only** (no `<text>` wrappers).
66+
67+
**Correct Implementation Pattern**:
68+
69+
```tsx
70+
// ✅ CORRECT: Return inline elements that go INSIDE the parent <text>
71+
export function renderMarkdown(markdown: string): ReactNode {
72+
const inlineElements = [
73+
<strong>Bold text</strong>,
74+
' and ',
75+
<em>italic text</em>
76+
]
77+
return <>{inlineElements}</>
78+
}
79+
80+
// In chat.tsx:
81+
<text wrap>
82+
{renderMarkdown(message.content)}
83+
</text>
84+
```
85+
86+
**Incorrect Pattern** (causes black screen):
87+
88+
```tsx
89+
// ❌ WRONG: Returning <text> components creates nested <text>
90+
export function renderMarkdown(markdown: string): ReactNode {
91+
return (
92+
<text wrap>
93+
<strong>Bold text</strong>
94+
</text>
95+
)
96+
}
97+
```
98+
99+
The implementation uses:
100+
- `markdownToInline()`: Converts markdown AST to array of inline JSX elements
101+
- `renderInlineContent()`: Renders inline styling (`<strong>`, `<em>`, `<span>`)
102+
- Returns a fragment `<>{inlineElements}</>` that can be safely placed inside parent `<text>`

cli/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
}
1616
},
1717
"scripts": {
18-
"dev": "bun run src/index.tsx",
18+
"dev": "bun run --watch src/index.tsx",
1919
"build": "bun build src/index.tsx --outdir dist --target node --format esm",
2020
"start": "bun run dist/index.js",
2121
"typecheck": "tsc --noEmit -p ."
@@ -26,11 +26,13 @@
2626
},
2727
"dependencies": {
2828
"@opentui/react": "^0.1.25",
29-
"react": "^19.0.0"
29+
"react": "^19.0.0",
30+
"remark-parse": "^11.0.0",
31+
"unified": "^11.0.0"
3032
},
3133
"devDependencies": {
32-
"@types/node": "22",
3334
"@types/bun": "^1.2.11",
35+
"@types/node": "22",
3436
"@types/react": "^18.3.12"
3537
}
3638
}

cli/src/chat.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from '@opentui/core'
77
import { render, useKeyboard, useRenderer } from '@opentui/react'
88
import { MultilineInput } from './multiline-input'
9+
import { renderMarkdown, hasMarkdown } from './markdown-renderer'
910
import {
1011
Fragment,
1112
useCallback,
@@ -389,7 +390,9 @@ This approach will improve _performance_ while maintaining **code clarity**.`
389390
marginBottom: isAi ? 1 : 0,
390391
}}
391392
>
392-
{message.content}
393+
{isAi && hasMarkdown(message.content)
394+
? renderMarkdown(message.content)
395+
: message.content}
393396
</text>
394397
</box>
395398
</box>

cli/src/markdown-renderer.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)