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

4.7 KiB
Raw Permalink Blame History

description
description
Improve markstream-vue performance for streaming chat UIs, large documents, Monaco code blocks, and heavy Mermaid or KaTeX content.

Performance Features & Tips

The renderer is optimized for streaming and large docs. Key features:

  • Incremental parsing for code blocks
  • Efficient DOM updates and memory optimizations
  • Monaco streaming updates
  • Progressive Mermaid rendering

Performance tips:

  • Stream long documents in chunks
  • Use MarkdownCodeBlockNode or renderCodeBlocksAsPre for non-editable code
  • Scope custom components to enable GC
  • Use setDefaultMathOptions at bootstrap

Bundle size workflow (maintainers)

If you are changing code paths that can impact build size (renderers, code blocks, optional peers), run this flow before merging:

  • pnpm build:analyze to produce visual reports (bundle-visualizer.html and bundle-visualizer-tailwind.html) and confirm what actually moved between chunks.
  • pnpm size:check to enforce local size budgets for dist, largest JS chunk, and npm pack --dry-run output.
  • Optional: tighten budgets in CI/locally with env vars like MAX_DIST_BYTES, MAX_JS_CHUNK_BYTES, MAX_PACK_TGZ_BYTES, MAX_PACK_UNPACKED_BYTES.

Keeping a Steady, Typewriter-Style Stream

Some AI or LLM sources send content in large bursts, which can feel like the preview is freezing and then dumping a whole block. To keep the UI feeling like a smooth, continuous typewriter:

  • Keep typewriter enabled on MarkdownRender (default) so non-code nodes fade in instead of popping instantly, and newly appended streamed text can replay a short local fade without dimming the whole block.
  • Tune the batching props: drop initialRenderBatchSize/renderBatchSize (for example 12/24), and add a small renderBatchDelay (20–30 ms). Even if the model sends a huge chunk, the renderer then inserts tiny slices each frame, producing a stable flow.
  • Throttle upstream updates if possible: instead of replacing content on every incoming hunk, debounce (50–100 ms) or split into smaller paragraphs so each render cycle operates on a “bite-sized” diff.
  • Defer heavy nodes by keeping deferNodesUntilVisible/viewportPriority turned on; expensive blocks (Mermaid/Monaco) will wait until they are near the viewport so the stream of text is never blocked.
  • Fall back for code blocks when a burst happens: disable codeBlockStream or temporarily use renderCodeBlocksAsPre during streaming so that syntax-highlighting work does not stall text updates.

These knobs keep DOM work under a predictable budget, so users perceive a calm, steady flow of content even when the backend sends data in erratic bursts.

Try this — tune rendering performance by enabling viewportPriority:

<script setup lang="ts">
import MarkdownRender from 'markstream-vue'

const md = '# Perf test'
</script>

<template>
  <MarkdownRender :content="md" :viewport-priority="true" />
</template>

Virtualization & DOM windows

MarkdownRender keeps a moving window of nodes in memory so extremely long documents stay responsive:

  • maxLiveNodes (default 320) caps how many fully rendered nodes remain in the DOM. Tune this based on your layout — lower values reduce memory but require slightly more placeholder churn; higher values prioritise scrollback.
  • liveNodeBuffer controls overscan on both sides of the focus window (default 60). Increase it when nodes vary wildly in height to avoid visible pop-in while scrolling fast.
  • deferNodesUntilVisible together with viewportPriority defers mounting heavy nodes (Mermaid, Monaco, KaTeX) until an observer reports they are close to the viewport.
  • batchRendering, initialRenderBatchSize, renderBatchSize, renderBatchDelay, and renderBatchBudgetMs govern how many nodes switch from placeholders to full components per frame. This incremental mode only runs when virtualization is disabled (:max-live-nodes="0"); otherwise the virtual window already limits DOM work, so nodes are rendered immediately without placeholders.

Example: Give the user a lighter DOM footprint while keeping scrollback smooth.

<script setup lang="ts">
import MarkdownRender from 'markstream-vue'

const md = '# Virtualized transcript'
</script>

<template>
  <MarkdownRender
    :content="md"
    :max-live-nodes="220"
    :live-node-buffer="40"
    :batch-rendering="true"
    :initial-render-batch-size="24"
    :render-batch-size="48"
    :render-batch-delay="24"
    :render-batch-budget-ms="8"
    :defer-nodes-until-visible="true"
    :viewport-priority="true"
  />
</template>

With these knobs you can keep large AI transcripts or docs under a predictable CPU budget while still presenting consistent scroll behaviour.