Skip to content

feat(core): add MarkdownRenderable with automatic table alignment - #433

Merged
kommander merged 48 commits into
anomalyco:mainfrom
remorses:markdown-renderable
Jan 18, 2026
Merged

kommander merged 48 commits into
anomalyco:mainfrom
remorses:markdown-renderable

Conversation

@remorses

@remorses remorses commented Dec 19, 2025 •

Copy link
Copy Markdown
Contributor
  • Adds MarkdownRenderable that extends CodeRenderable with markdown-specific features
  • Uses marked to parse markdown and automatically align table columns based on content width
  • Accounts for concealed characters (formatting markers like backticks, bold, italic) when calculating column widths
  • Related to feat: add automatic markdown table alignment #369 (table alignment) but this implements it as a dedicated Markdown renderable instead of modifying CodeRenderable
Screenshot 2025-12-20 at 00 44 44

Tables are rendered via flexbox boxes:

Here's the current structure:
tableBox (flexDirection: "row")
├── columnBox[0] (flexDirection: "column", border: top/bottom/left)
│   ├── headerBox (border: bottom)
│   │   └── TextRenderable "Emoji" (paddingLeft: 1, paddingRight: 1)
│   ├── cellBox (border: bottom)
│   │   └── TextRenderable "🎉"
│   ├── cellBox (border: bottom)
│   │   └── TextRenderable "🚀"
│   └── TextRenderable "日本語"
│
├── columnBox[1] (flexDirection: "column", border: all sides)
│   ├── headerBox
│   │   └── TextRenderable "Name"
│   ├── cellBox
│   │   └── TextRenderable "Party"
│   ...

@remorses
remorses marked this pull request as ready for review December 20, 2025 00:10
Copilot AI review requested due to automatic review settings December 20, 2025 00:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new MarkdownRenderable component that extends the rendering capabilities of OpenTUI with markdown-specific features. The implementation uses the marked library to parse markdown and renders tables with automatic column sizing through a BoxRenderable-based layout system.

Key changes:

  • Introduces MarkdownRenderable class with support for tables, code blocks, headings, lists, blockquotes, and inline formatting
  • Implements a conceal mode that hides markdown formatting markers (e.g., **, *, `) while preserving styled text
  • Adds comprehensive test coverage with 32 test cases covering various markdown features

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/core/src/renderables/Markdown.ts New MarkdownRenderable implementation with table rendering, inline formatting, and concealment logic
packages/core/src/renderables/tests/Markdown.test.ts Comprehensive test suite covering tables, formatting, code blocks, and conceal modes
packages/core/src/renderables/index.ts Exports the new Markdown renderable
packages/core/src/examples/markdown-demo.ts Interactive demo showcasing markdown features with theme switching
packages/react/src/types/components.ts TypeScript types for React MarkdownRenderable component
packages/solid/src/types/elements.ts TypeScript types for Solid MarkdownRenderable component
packages/core/package.json Adds marked@^17.0.1 dependency
bun.lock Lock file updates for marked dependency

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@kommander

Copy link
Copy Markdown
Collaborator

Uhh looks good! I need to work through that.

@kommander

Copy link
Copy Markdown
Collaborator

It currently does not support streaming markdown properly. I can look into that. It needs an append API to append markdown chunks when they come in from an LLM response, only reparsing with marked what is not yet a finished block and not yet added as renderables, without removing and recreating all renderables for every update.

@kommander

Copy link
Copy Markdown
Collaborator

I made it update the tables in-place instead of recreating all elements, which is somewhat better. I found that when streaming a lot of markdown (endless mode in the demo), it becomes slower and slower. Seems like something is resetting the parserState so it re-parses the markdown and/or the tables are re-created for incremental updates which is expensive and in long content takes >10ms. There are some perf updates for text rendering coming in core, but I wouldn't rely on that.

I wonder if there is a better way to render tables, without relying on yoga layout, as for large tables it would be quite a lot of nested nodes.

@remorses

Copy link
Copy Markdown
Contributor Author

Thank you for fixing the reset. I think using Yoga is completely fine. I noticed no lags rendering a lot of tables in opencode with the markdown renderable.

@kommander

Copy link
Copy Markdown
Collaborator
Screen.Recording.2025-12-29.at.11.32.01.mov

Check the "Overall" render time in the stats, it should be low and stable, but it increases the more content is being streamed. In the markdown demo when I set stream speed to fastest and shift+E for endless stream (which can be stopped with shift+X again).

@remorses

remorses commented Dec 29, 2025 •

Copy link
Copy Markdown
Contributor Author

I found what was the issue with the lower FPS when the is content growing, the issue is layout recalculations

Every time a yoga node changes we also mark the parents dirty, so yoga calls measure functions again which can be slow when there are a lot of nodes

Using bun-yoga makes this problem less noticeable but does not remove it

To make layout calculation faster during streaming with a lot of content there would need to be a way to cache layout for nodes if content did not change like the browser does

Screenshot 2025-12-29 at 19 40 09

@remorses

Copy link
Copy Markdown
Contributor Author

Adding a cache for measureFunc in packages/core/src/renderables/TextBufferRenderable.ts lowers the time 3x, Yoga seems to call measure 3 times but we can cache it

GriffinSchneider added a commit to GriffinSchneider/claude-code-history that referenced this pull request Jan 4, 2026
Switch to this OpenTUI PR with built-in Markdown rendering:
anomalyco/opentui#433

Also there were an increasing number of hard to nail down
layout/rendering issues - text overlapping other text, random characters
not getting cleared, etc. I had Claude remove a bunch of functionality
and then gradually add things back in. Rendering seems solid now but
some features are missing.
@kommander

Copy link
Copy Markdown
Collaborator

I'll do the performance improvements this week and then this should be good.

@kynnyhsap

Copy link
Copy Markdown

Hope this can be merged soon

@pkg-pr-new

pkg-pr-new Bot commented Jan 14, 2026 •

Copy link
Copy Markdown
@opentui/core

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core@433

@opentui/react

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/react@433

@opentui/solid

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/solid@433

@opentui/core-darwin-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-arm64@433

@opentui/core-darwin-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-x64@433

@opentui/core-linux-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-arm64@433

@opentui/core-linux-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-x64@433

@opentui/core-win32-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-arm64@433

@opentui/core-win32-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-x64@433

commit: 0d229f1

remorses and others added 5 commits January 15, 2026 01:11
Two issues fixed:
1. Use --all flag to enable Zig cross-compilation for all platforms
   (darwin, linux, windows) instead of only building for the CI runner's
   platform (linux)
2. Fix the native package paths - packages are scoped under @opentui/
   (e.g., @opentui/core-darwin-arm64) not flat opentui-* directories

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@kommander

Copy link
Copy Markdown
Collaborator

I still have some concerns about performance with marked, the current incremental parsing seems to not properly work.

Will merge this anyway now and we can iterate on it. I would put it into opencode with a flag to roll it out to a broader audience with the ability to switch back and forth should it not behave as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants