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.
This commit is contained in:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,166 @@
# markstream-react
React renderer that consumes the structured AST output from `stream-markdown-parser` and renders it with lightweight semantic HTML components. This is the React counter-part to the Vue renderer that powers `markstream-vue`.
## Development
```bash
pnpm --filter markstream-react dev
```
## Build
```bash
pnpm --filter markstream-react build
pnpm --filter markstream-react build:analyze
pnpm --filter markstream-react size:check
```
## Usage
```tsx
import NodeRenderer from 'markstream-react'
import 'markstream-react/index.css'
export default function Article({ markdown }: { markdown: string }) {
return (
<NodeRenderer content={markdown} />
)
}
```
If your app scales root font size on mobile (`html` / `body`), use `markstream-react/index.px.css` to prevent `rem`-based global scaling side effects.
You can also pass a pre-parsed `nodes` array if you already have AST data.
## Streaming best practices
- For high-frequency SSE / token streaming, prefer parsing outside the component and pass `nodes` instead of reparsing the full `content` string every chunk.
- Keep `viewportPriority` enabled unless you explicitly want eager rendering. Mermaid / Monaco / D2 blocks now stay idle while offscreen and resume when they approach the viewport.
```tsx
import NodeRenderer from 'markstream-react'
export default function StreamView({ nodes, final }: { nodes: any[], final: boolean }) {
return (
<NodeRenderer
nodes={nodes}
final={final}
viewportPriority
deferNodesUntilVisible
/>
)
}
```
## Heavy-node prop forwarding
`NodeRenderer` can forward renderer-level props directly into Mermaid / D2 / Infographic blocks:
```tsx
<NodeRenderer
content={markdown}
mermaidProps={{
showHeader: false,
renderDebounceMs: 180,
previewPollDelayMs: 500,
}}
d2Props={{ progressiveIntervalMs: 500 }}
infographicProps={{ showHeader: false }}
/>
```
Notes:
- These props are forwarded to the built-in Mermaid / D2 / Infographic blocks and to custom `mermaid` / `d2` / `infographic` overrides registered with `setCustomComponents(...)`.
- `viewportPriority` applies to those heavy nodes too, so offscreen graphs will not keep doing background work while the text stream is still updating.
## Language-specific code block overrides
You can register a custom component under a fenced language key without wrapping the generic `code_block` renderer:
```tsx
import type { NodeComponentProps } from 'markstream-react'
import { setCustomComponents } from 'markstream-react'
function EChartsBlockNode(props: NodeComponentProps<any>) {
return <div data-language={String(props.node?.language)}>{String(props.node?.code || '')}</div>
}
setCustomComponents('docs', {
echarts: EChartsBlockNode,
})
```
Notes:
- `echarts` only catches fences whose language is `echarts`.
- Code block routing priority is exact language key -> built-in `mermaid` / `d2` / `infographic` routes -> `code_block`.
- Custom `mermaid` / `d2` / `infographic` overrides keep their specialized top-level props; other custom language keys use the normal custom component contract (`node`, `ctx`, `renderNode`, and friends).
## Mermaid tuning
Common `mermaidProps` keys for streaming scenarios:
- `renderDebounceMs`: delay progressive work during rapid token bursts.
- `contentStableDelayMs`: how long source mode waits before auto-switching back to preview when content stabilizes.
- `previewPollDelayMs`: initial delay before preview polling tries to upgrade a partial preview into a full render.
- `previewPollMaxDelayMs`: cap for preview polling backoff.
- `previewPollMaxAttempts`: maximum retry count while the Mermaid source is still incomplete.
## Bundle size notes
- Optional peers are not bundled; install only what you use (`stream-monaco`, `stream-markdown`, `mermaid`, `katex`, etc.).
- Infrequent language icons are split into an async chunk and loaded on demand.
- To avoid first-hit fallback icons, preload once when the app is idle:
```tsx
import { preloadExtendedLanguageIcons } from 'markstream-react'
if (typeof window !== 'undefined')
void preloadExtendedLanguageIcons()
```
## Tailwind
- Non-Tailwind projects: keep importing `markstream-react/index.css` (includes precompiled utilities for the renderer).
- Tailwind projects (avoid duplicate utilities): import `markstream-react/index.tailwind.css` and add `require('markstream-react/tailwind')` to your `tailwind.config.js` `content`.
## Custom components (e.g. `<thinking>`)
Custom tag-like blocks are exposed as nodes with `type: 'thinking'` (the tag name, no angle brackets) when you register the tag in `customHtmlTags` or register a custom component mapping for it.
```tsx
import type { NodeComponentProps } from 'markstream-react'
import NodeRenderer, { setCustomComponents } from 'markstream-react'
function ThinkingNode(props: NodeComponentProps<{ type: 'thinking', content: string }>) {
const { node, ctx } = props
return (
<div className="thinking-node">
<div className="thinking-title">Thinking</div>
<NodeRenderer
content={node.content}
customId={ctx?.customId}
isDark={ctx?.isDark}
typewriter={false}
batchRendering={false}
deferNodesUntilVisible={false}
viewportPriority={false}
maxLiveNodes={0}
/>
</div>
)
}
setCustomComponents('chat', { thinking: ThinkingNode })
```
## Type exports
`markstream-react` now exposes the core public types directly from the package root, including:
- `NodeRendererProps`
- `NodeComponentProps`
- `RenderContext`
- `RenderNodeFn`
- `CustomComponentMap`
- `CodeBlockMonacoOptions`