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:
@@ -0,0 +1,107 @@
|
||||
# Heavy block remount jank
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| 优先级 | P1 |
|
||||
| 分类 | renderer / heavy nodes / async components |
|
||||
| 状态 | done |
|
||||
| 创建日期 | 2026-04-30 |
|
||||
| 关联文件 | `src/components/CodeBlockNode/CodeBlockNode.vue`、`src/components/CodeBlockNode/monaco.ts`、`src/components/CodeBlockNode/runtime.ts`、`src/components/MermaidBlockNode/MermaidBlockNode.vue`、`src/components/InfographicBlockNode/InfographicBlockNode.vue`、`src/components/NodeRenderer/NodeRenderer.vue`、`src/utils/diagramHeight.ts`、`src/types/component-props.ts`、`packages/markstream-vue2/src/components/NodeRenderer/NodeRenderer.vue`、`packages/markstream-vue2/src/utils/diagramHeight.ts`、`packages/markstream-react/src/renderers/renderNode.tsx`、`packages/markstream-angular/src/components/shared/node-outlet-helpers.ts` |
|
||||
|
||||
## 问题
|
||||
|
||||
使用虚拟滚动并恢复已保存 `scrollTop` 的上层应用,在重新挂载已经加载过的内容时,重型 Markdown block 会出现可见抖动。最明显的是以下几类节点:
|
||||
|
||||
- `CodeBlockNode` 重新挂载时先显示 `PreCodeNode`,再切换到 Monaco code block。
|
||||
- `MermaidBlockNode` 在 async component resolve 前可能先出现空白或源码 fallback,再替换成图表。
|
||||
- `InfographicBlockNode` 存在同类问题:async component 没有 loading shell,并且组件先以源码模式初始化,mounted 后才切到 preview。
|
||||
|
||||
这些行为对冷启动首次渲染是合理的,但对已加载内容的 remount 不稳定。上层虚拟滚动已经恢复了 `scrollTop` 后,block 的 DOM 结构和高度又发生变化,就会把下方正文推开,表现为闪烁、内容瞬移,或者落入虚拟列表 stale spacer 造成的空白区域。
|
||||
|
||||
这是 `markstream-vue` 层的问题。上层应用可以保存和恢复滚动位置,但如果组件首帧高度、fallback 模式和最终渲染结果不一致,应用层无法稳定消除这类抖动。
|
||||
|
||||
后续排查确认同类风险不只存在于 Vue 3 包。`markstream-vue2` 的 Mermaid/Infographic 仍然使用异步组件首帧空壳,`markstream-react` 和 `markstream-angular` 虽然没有同样的 async component 空壳,但 Mermaid/Infographic 首帧仍缺少稳定的高度估算,切换或刷新时也可能推动后续正文。
|
||||
|
||||
## 根因
|
||||
|
||||
- code block 是否显示 fallback 只依赖当前组件实例是否完成 editor mount,没有感知共享 Monaco runtime 是否已经预热完成。
|
||||
- Mermaid 和 Infographic 使用默认 async component loading 行为,首帧没有预留最终图表容器的形态和高度。
|
||||
- Mermaid 和 Infographic 的 preview 高度需要等真实 SVG/HTML 渲染后才能测量。
|
||||
- Infographic 默认 `showSource = true`,loader 可用时再在 mounted 后切到 preview,导致 remount 时可能先显示源码或空白。
|
||||
- loading shell 和最终 header 的 padding、图标、action button 占位不一致时,即使只有几像素差异,也会让图表下方正文产生可见位移。
|
||||
|
||||
## 修复
|
||||
|
||||
### CodeBlockNode
|
||||
|
||||
- 增加共享 code block runtime ready 状态和 `preloadCodeBlockRuntime()`。
|
||||
- `getUseMonaco()` 在 Monaco 以及 preload 工作完成后标记 runtime ready。
|
||||
- runtime 已 ready 的 warm remount 不再显示可见 `PreCodeNode` fallback。
|
||||
- cold start 仍然保留 fallback,直到 editor mount 和初始高度稳定。
|
||||
- editor 创建失败时继续显示 fallback,保证代码内容仍可读。
|
||||
- 对齐 fallback 的 font size、line height、padding 和估算内容高度,减少 fallback 到 Monaco 过程中的高度变化。
|
||||
|
||||
### MermaidBlockNode
|
||||
|
||||
- 增加基于图表源码的 preview 高度估算。
|
||||
- `NodeRenderer` 在调用方没有显式传入时自动注入 `estimatedPreviewHeightPx`。
|
||||
- async component resolve 前立即渲染同形态 loading shell,包括 header 和预留 preview 区域。
|
||||
- Mermaid loader 已启用时,组件直接以 preview 模式初始化,不再先显示源码。
|
||||
- 最终 preview 在真实内容可用前使用估算高度作为下限。
|
||||
|
||||
### InfographicBlockNode
|
||||
|
||||
- 增加和 Mermaid 同类的 preview 高度估算与 async loading shell。
|
||||
- Infographic loader 已启用时,组件直接以 preview 模式初始化。
|
||||
- 最终 preview 在真实内容可用前使用估算高度作为下限。
|
||||
- loading shell 的 header 尺寸与最终 header 对齐,包括图标和 action button 占位,避免 async component resolve 时推动下方正文。
|
||||
|
||||
### 多框架对齐
|
||||
|
||||
- `markstream-vue2` 补齐 Mermaid/Infographic 的高度估算、async loading shell 和 `estimatedPreviewHeightPx` 注入。
|
||||
- `markstream-react` 在 client/server render path 中都为 Mermaid/Infographic 注入稳定高度估算,并让组件首帧使用该高度。
|
||||
- `markstream-angular` 在 NodeOutlet custom inputs 和内置 Mermaid/Infographic 组件中使用同一套高度估算,避免 custom renderer 和 builtin renderer 表现不一致。
|
||||
- React/Angular 的 CodeBlock/MarkdownCodeBlock 没有 Vue3 `PreCodeNode -> CodeBlockNode` 的同一条切换链路,本次只补图表高度稳定性,不改它们的 code block 架构。
|
||||
|
||||
## 为什么这样改
|
||||
|
||||
这次修复把责任放在产生不稳定 DOM 的组件内部:
|
||||
|
||||
- 上层应用负责保存滚动位置、恢复滚动位置、维护虚拟列表 row measurement。
|
||||
- `markstream-vue` 负责让重型 block 的首帧 DOM 形态和高度尽量接近最终状态,避免破坏上层滚动恢复。
|
||||
|
||||
使用 loading shell 和 estimated height floor 比关闭虚拟滚动、或者要求每个应用分别特判 Mermaid/Infographic/CodeBlock 更收敛。optional peer fallback 行为也被保留:Mermaid、Infographic 或 Monaco 不可用时,仍然回退到可读的 preformatted code。
|
||||
|
||||
## 验证
|
||||
|
||||
库侧验证:
|
||||
|
||||
```bash
|
||||
pnpm vitest run test/node-renderer-heavy-node-props.test.ts
|
||||
pnpm vitest run test/vue2-node-renderer-heavy-node-props.test.ts
|
||||
pnpm vitest run test/react-render-node-heavy-props.test.ts test/react-diagram-block-parity.test.tsx
|
||||
pnpm vitest run test/markstream-angular-node-outlet.test.ts
|
||||
pnpm vitest run test/code-block-editor-lock.test.ts
|
||||
pnpm vitest run test/mermaid-streaming-preview-regression.test.ts
|
||||
pnpm lint
|
||||
pnpm typecheck
|
||||
pnpm build
|
||||
git diff --check
|
||||
```
|
||||
|
||||
通过 `dimcode-app` workspace link 的集成验证:
|
||||
|
||||
```bash
|
||||
pnpm test:e2e:scroll
|
||||
DIMCODE_E2E_REAL_THREAD_IDS=924740a7-8e64-454b-b4bf-7a3b462ce6bf,s_1777337966876_0 pnpm test:e2e:scroll
|
||||
pnpm --filter @dimcode/renderer typecheck
|
||||
git diff --check
|
||||
```
|
||||
|
||||
e2e 覆盖:
|
||||
|
||||
- 已加载 thread 来回切换后,保存的滚动位置不变化。
|
||||
- 当前 thread 滚动后刷新,恢复到刷新前位置。
|
||||
- code block warm remount 不出现可见 `PreCodeNode` fallback。
|
||||
- Mermaid 和 code block 同时出现在可视区域时,切换和刷新后 row 位置、高度、header 和后续正文稳定。
|
||||
- Infographic 和 code block 同时出现在可视区域时,切换和刷新后 row 位置、高度、header 和后续正文稳定。
|
||||
@@ -0,0 +1,5 @@
|
||||
# Issues
|
||||
|
||||
| Issue | 状态 |
|
||||
|-------|------|
|
||||
| [001 - Heavy block remount jank](./001-heavy-block-remount-jank.md) | done |
|
||||
Reference in New Issue
Block a user