rich-text-vista: RichTextVista: The HarmonyOS text engine that turns Markdown, HTML, and custom nodes into one native string
Inside ByteDance’s pluggable rich-text pipeline, where a CommonMark-based AST becomes a fast rendering layer instead of a forest of spans.

RichTextVista is a high-performance, extensible rich text component designed for HarmonyOS applications, supporting diverse rich text styling capabilities.
- RichTextVista treats rich text as a compilation problem, not a widget problem, by normalizing Markdown, HTML, plain text, and AST input into one canonical path.
- Its biggest payoff is a single native `MutableStyledString`, which keeps HarmonyOS from turning rich text into a forest of nested spans.
- The plugin layer matters because it lets custom nodes join the same rendering pipeline without forcing the UI to learn one-off branches.
- BISU keeps style state flat, which makes the tree easier to traverse and the renderer easier to extend.
RichTextVista behaves less like a rich text widget and more like a compiler. It takes plain text, Markdown, HTML, or a raw node tree, normalizes them into one canonical AST, then compiles that into a single native `MutableStyledString`. On HarmonyOS, that is the right trade. The UI stays simple, while the hard work happens before render.
Why HarmonyOS wants this shape
Rich text gets expensive when every style becomes a node the UI has to manage. Nested `Span` and `Text` trees are easy to start with, then they become hard to reuse, hard to cache, and awkward in scrolling surfaces. RichTextVista pushes complexity upstream so the view layer receives one styled string and one controller instead of a thicket of fragments.
| Approach | Input handling | Internal model | Output | Extension path | Best fit |
|---|---|---|---|---|---|
| RichTextVista | Markdown, HTML, plain text, raw AST | CommonMark-shaped AST | One `MutableStyledString` through `TextController` | Pluggable producers and custom nodes | HarmonyOS feeds and content-heavy screens |
| Nested span trees | Usually one format at a time | UI nodes as structure | Many `Span` and `Text` children | Tedious special cases | Small, simple snippets |
| Parser-first widgets | Markdown or HTML only | Parser-specific tree | Widget fragments or mixed output | Limited or framework-specific | Quick prototypes |
That design gives the project a narrow waist. Every input format is allowed to be messy, but every output path is forced through one native shape. The result is not a bigger text feature set. It is a cleaner contract.
The AST is the real product
The hub is `RichTextRepresentation.ets`. Plain text gets wrapped, Markdown goes through `Markdown2AST.ets`, HTML goes through `HTML2AST.ets`, and raw nodes are carried forward without pretending they came from somewhere else. That lets the rest of the pipeline read like one language instead of a collection of format adapters.
const rep = new RichTextRepresentation({
markdown,
extensions: [mentionExtension]
})
const ast = rep.getElements()
const styled = assembleInlineElementsToMutableStyledString(ast)
textController.setStyledString(styled)
That CommonMark-shaped tree is the real product. Once the engine has a normalized node graph, the renderer can stop asking what the original input looked like and start asking how to map nodes into styles.
Pluggable styles without exploding the tree
From there, `ParsePlugin.ets` and `AssembleBasicMutableStyledString.ets` turn the AST into output. The dispatcher and producer pattern keep built-in tags and custom nodes on the same rails. A mention, an emoji, or a bespoke token can join the flow without forcing the whole renderer to learn a new branch.
const customNode = {
tag: 'CUSTOM',
attributes: { kind: '@mention', userId: '123' }
}
const extensionList = [mentionProducer]
const result = assembleInlineElementsToMutableStyledString(customNode, extensionList)
That matters more than it sounds. Extension points are where rich text engines usually crack, because every special case wants its own shortcut. RichTextVista instead makes the shortcut a plugin.
BISU keeps style state flat
BISU is a small acronym with a big payoff: Bold, Italic, Stroke, Underline. Instead of multiplying node types for every style combination, the project keeps style state compact in attributes and lets traversal stay regular.
That flatness is why the tree stays readable. Fewer node classes means fewer branches in the renderer, and fewer branches usually means fewer surprises when the content gets weird.
What it looks like against the alternatives
RichTextVista is not trying to replace every rich text tool. It is carving out a specific sweet spot: native output, heterogeneous input, and extension points that do not wreck the render tree.
| Project shape | Strength | Cost | Where it wins |
|---|---|---|---|
| RichTextVista | One native text surface with pluggable inputs | Preprocessing and a custom pipeline | HarmonyOS apps that need extensible rich text |
| Nested span trees | Direct and familiar | Component bloat as content grows | Tiny snippets and simple screens |
| Parser-only widgets | Fast to prototype | Limited output control | Narrow content formats |
If you need a full editor, this is not that. If you need a reliable bridge from content formats to HarmonyOS text, the shape makes sense immediately. The project sits between a formatting library and a rendering runtime, and that is exactly why it feels useful.
What the repo says about the Lark team
Early-stage projects show their seams in the issue tracker, and that is useful here. One user noted, "不支持 html 中的列表". That complaint does not undercut the design. It shows the repo is still accumulating edge cases while the core architecture stays coherent.
不支持 html 中的列表
The repository still looks young, but the structure is disciplined. `commonmark/` handles parsing, `rich_text_core/` carries the HarmonyOS-specific engine, and `entry/` demonstrates practical use cases like at-mentions. That is the footprint of an internal tool that was shaped for real workloads before it was opened up.
That is the most revealing line in the repo. It does not pitch novelty for its own sake. It describes a component that is meant to survive real product pressure, while still staying open enough for custom content and future growth.





