Files
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

170 lines
6.2 KiB
Markdown

---
description: Choose between content and nodes, understand the render pipeline, and wire markstream-vue into Vite, VitePress, Nuxt, and streaming UIs.
---
# Usage & API
This page shows how to wire `markstream-vue` into common stacks, how the parser fits into the renderer, and which docs to visit when something looks odd (reset order, Tailwind/UnoCSS layers, VitePress integration).
## Choose `content` or `nodes` first
| Situation | Recommended input |
|-----------|-------------------|
| Docs pages, static articles, low-frequency updates | `content` |
| SSE, token streaming, AI chat, or frequent incremental updates | `nodes` + `final` |
| SSR or worker-preparsed content | `nodes` |
If you only need built-in configuration, stay in this page and [Props & Options](/guide/props). If you need to replace rendering behavior, jump to [Override Built-in Components](/guide/component-overrides).
If your real goal is an AI chat surface, token stream, or SSE response viewer, use the dedicated [AI Chat & Streaming](/guide/ai-chat-streaming) path instead of piecing the docs together manually.
If your real goal is a docs site or VitePress theme, use [Docs Site & VitePress](/guide/vitepress-docs-integration) for the end-to-end `content` + `enhanceApp` + CSS setup.
## Minimal render
```vue twoslash
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'
const doc = '# Usage example\n\nSupports **streaming** nodes.'
</script>
<template>
<MarkdownRender custom-id="docs" :content="doc" />
</template>
```
```css
@import 'modern-css-reset';
@layer components {
@import 'markstream-vue/index.css';
}
```
## VitePress + custom tags
If this is your main use case rather than a one-off example, continue with the dedicated [Docs Site & VitePress](/guide/vitepress-docs-integration) path after this section.
In VitePress, register your custom node component once in `enhanceApp`, then use `custom-html-tags` on `MarkdownRender` to let the parser emit custom nodes automatically.
```ts twoslash
import type { Component } from 'vue'
import MarkdownRender, { setCustomComponents } from 'markstream-vue'
// docs/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import 'markstream-vue/index.css'
declare const ThinkingNode: Component
export default {
extends: DefaultTheme,
enhanceApp() {
setCustomComponents('docs', { thinking: ThinkingNode })
},
}
```
```md
<!-- in a VitePress page -->
<MarkdownRender
custom-id="docs"
:custom-html-tags="['thinking']"
:content="source"
/>
```
Tip: unknown HTML-like tags (such as `<question>`) render as raw HTML elements once closed. Incomplete or malformed fragments stay as plain text so they do not swallow nearby Markdown. Add the tag name to `custom-html-tags` when you want custom node output (type + attrs/content).
## Parser pipeline
```ts twoslash
import { getMarkdown, parseMarkdownToStructure } from 'markstream-vue'
const md = getMarkdown()
const nodes = parseMarkdownToStructure('# Title', md)
// pass nodes to <MarkdownRender :nodes="nodes" />
```
- `getMarkdown(msgId?, options?)` returns a configured `markdown-it-ts` instance.
- `parseMarkdownToStructure(content, md)` transforms a Markdown string into the AST consumed by the renderer.
- Combine with `setCustomComponents(id?, mapping)` to swap node renderers for a given `custom-id`.
## Streaming recommendation
For low-frequency updates, passing `content` directly is convenient. For chat-style token streams or long documents, prefer parsing outside the component and passing `nodes` so Vue only reconciles the AST you hand it:
```ts twoslash
import { getMarkdown, parseMarkdownToStructure } from 'markstream-vue'
const md = getMarkdown('chat')
declare const source: string
declare const isFinalChunk: boolean
const nodes = parseMarkdownToStructure(source, md, { final: isFinalChunk })
```
```vue twoslash
<script setup lang="ts">
import type { BaseNode } from 'markstream-vue'
import MarkdownRender from 'markstream-vue'
const nodes: BaseNode[] = []
const isFinalChunk = false
</script>
<template>
<MarkdownRender :nodes="nodes" :final="isFinalChunk" />
</template>
```
That avoids reparsing the full Markdown string inside `MarkdownRender` on every tiny content update, which is usually the biggest win for SSE / AI output.
For a full end-to-end rollout order, peer selection, and chat-specific tuning, continue with [AI Chat & Streaming](/guide/ai-chat-streaming).
## Component matrix
For a full list of components and props, visit [Components & node renderers](/guide/components). Highlights:
- `CodeBlockNode` — Monaco-powered blocks (requires `stream-monaco`).
- `MarkdownCodeBlockNode` — Shiki-based lightweight highlighting.
- `MermaidBlockNode` — requires `mermaid` ≥ 11 (no extra CSS).
- `D2BlockNode` — requires `@terrastruct/d2` (no extra CSS).
- `ImageNode` — emits `click`, `load`, `error` for custom previews.
## Styling reminders
1. **Reset first** (`modern-css-reset`, `@tailwind base`, `@unocss/reset`), then import `markstream-vue` styles.
2. **Use CSS layers** when Tailwind/UnoCSS is active (`@layer components { @import 'markstream-vue/index.css' }`).
3. **UNO/Tailwind conflicts** — follow the [Tailwind guide](/guide/tailwind) (includes UnoCSS examples) to prevent utilities from overriding renderer styles.
4. **Peer CSS** — KaTeX needs its own CSS; Mermaid/D2 do not. Monaco does not require extra CSS.
## CSS scoping (important)
The package CSS is scoped under an internal `.markstream-vue` container to minimize global style conflicts (Tailwind utilities and theme variables included).
- When you use `MarkdownRender`, you get this container automatically.
- If you render exported node components on their own (e.g. `PreCodeNode`, `FootnoteNode`), wrap them with a container element:
```vue twoslash
<script setup lang="ts">
import type { PreCodeNodeProps } from 'markstream-vue'
import { PreCodeNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'ts',
code: 'console.log(1)',
raw: 'console.log(1)',
} satisfies PreCodeNodeProps['node']
</script>
<template>
<div class="markstream-vue">
<PreCodeNode :node="node" />
</div>
</template>
```
If visuals still look wrong, reproduce the issue inside the playground (`pnpm play`) and cross-check with the troubleshooting guide before filing a bug.