Repository navigation
Description
Use Case
OpenCode recently added Unicode LaTeX rendering through OpenTUI's fenced-code renderer API (opencode#45339). It works for latex and math fences, but models commonly emit $$...$$, \[...\], \(...\), and $...$ instead. Those forms do not reach the renderer.
We would like to support those delimiters without replacing OpenTUI's Markdown renderer or preprocessing Markdown into synthetic code fences.
Current Boundary
Checked against @opentui/[email protected] and the current upstream source:
MarkdownOptions.renderNodelets an application override rendering of parsed blocks, but does not expose custom tokenization.parseMarkdownIncrementalcallsLexer.lexinternally with no caller-supplied lexer or extension options.renderInlineTokenis a private path that emits text chunks, not a public inline equivalent ofrenderNode.
OpenCode's plugin API currently exposes only code-block renderer registration, so it would also need to expose any new upstream capabilities.
Requested Capability
Could OpenTUI expose per-renderable Markdown tokenization extensions and inline rendering hooks? The exact API is open for discussion. The goal is to let an application register custom block and inline syntax while OpenTUI continues to own Markdown parsing, layout, and incremental updates.
For the LaTeX use case:
- Display delimiters should produce a math block that can use the existing multiline Unicode renderer.
- Inline delimiters should support compact styled text within a paragraph. This does not require arbitrary multiline renderables embedded in a text line.
- Extensions should work consistently in paragraphs, lists, blockquotes, and table cells, including streaming and finalized content.
- The integration should preserve code spans and code blocks, delimiter escaping, surrounding styles, and links. Single-dollar currency detection can remain a policy of the math extension.
- Partial delimiters and unfinished formulas during streaming should not require the application to maintain a competing Markdown parser.
- Configuration should be scoped to the renderable rather than requiring global Marked mutations.
This is a request for generic extension points, not to move the LaTeX parser/layout engine into OpenTUI. The math engine and delimiter rules can remain in an application plugin.
- Not Actually Kit
Activity
simonklee commentedon Sep 4, 2026
I just ran into this the other day myself. started prototyping something.
FasterPHP commentedon Oct 11, 2026
Version and environment:
@opentui/core0.5.12 (unchanged on 0.5.17 andmain), Bun 1.4.0.A second consumer for this, with different extensions: we are building an Agent Client Protocol (ACP) terminal client that renders agent Markdown with
MarkdownRenderable, and it needs footnotes ([^1]and[^1]: ...), maths ($a_1$,\(...\),$$...$$) and GitHub alerts (> [!NOTE]). Maths is the case that cannot be done after lexing: marked reads$a_1$ and $b_2$as emphasis and\(as an escape, so by the timerenderNoderuns the information is gone. We currently work around it with a pre-pass over the source that swaps maths spans and footnote references for private-use placeholders outside code, which is fragile (it must escape any private-use characters in the agent's text, track fences itself, and cope with streaming).Minimal reproduction (
markdown-extension-hook.ts, about 55 lines): a block extension registered withmarked.use()yields acallouttoken whenmarkedis used directly, but never reachesMarkdownRenderable, whether throughrenderNodeor at all:Why
marked.use()cannot work (packages/core/srcatv0.5.12):renderables/markdown-parser.tslines 20, 65 and 73 callLexer.lex(src, { gfm: true })with its own options object, and marked uses a passed options object instead of the defaults thatmarked.use()changes (the point made in PR #1345's description). In the published bundlemarkedis also inlined (index.bun.js:9453,[email protected]), so the application's ownmarkedis a different instance anyway.MarkdownOptions(renderables/Markdown.ts:87to126) hasrenderNodefor top-level blocks only and no tokenizer option.What we would need, in line with #1464's list: a per-renderable
extensionsoption (marked'sMarkedExtension[]shape:extensionswithlevel,startandtokenizer, applied to the sameLexer.lexcalls so the incremental parser and the unstable trailing block keep working), and a way to render an inline custom token (todayrenderInlineTokenis private,Markdown.ts:575, andrenderNodeis block level). Block tokens could go throughrenderNodeunchanged. Scoped to the renderable, not a globalmarked.use().If a prototype exists, we would gladly test it against a footnotes and maths extension and our streaming test corpus.
Reproduction:
markdown-extension-hook.tsviralcodex commentedon Oct 23, 2026
@simonklee is this still relevant or can be closed?
core: let a Markdown hook rewrite prose spans (#2023)
rust: parse Markdown math with an extensions option (#2072)