fix(markdown): restore streaming code highlight via markstream-vue 1.0.7 (#155)

This commit is contained in:
qer 2026-07-29 21:38:46 +08:00 committed by GitHub
parent 8abde94b2b
commit dd04056d5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 383 additions and 53 deletions

View file

@ -0,0 +1,5 @@
---
"kimi-code-app": patch
---
修复流式输出中代码块的语法高亮偶尔丢失、定居后仍显示为纯文本的问题。

View file

@ -48,7 +48,7 @@
"electron": "43.1.1",
"electron-builder": "26.15.3",
"katex": "^0.17.0",
"markstream-vue": "^1.0.4",
"markstream-vue": "^1.0.7",
"mermaid": "^11.15.0",
"photoswipe": "^5.4.4",
"shiki": "^4.3.0",

View file

@ -26,7 +26,7 @@
"@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0",
"katex": "^0.17.0",
"markstream-vue": "^1.0.4",
"markstream-vue": "^1.0.7",
"mermaid": "^11.15.0",
"photoswipe": "^5.4.4",
"shiki": "^4.3.0",

View file

@ -14,7 +14,23 @@ const preset = kimiRendererViteConfig({
export default defineConfig({
plugins: preset.plugins,
resolve: {
alias: {
// markstream-vue 1.0.7's KaTeX worker imports `katex/dist/contrib/mhchem`,
// which is not in katex's exports map (`./contrib/mhchem` is). Vite's
// bundler falls back to filesystem resolution, but vitest's Node-ESM
// loader is strict — point it at the exported subpath.
'katex/dist/contrib/mhchem': 'katex/contrib/mhchem',
},
},
test: {
include: ['src/**/*.test.ts', 'test/**/*.test.ts'],
server: {
deps: {
// Externalized deps load via Node ESM, where the alias above cannot
// reach them; run markstream-vue through the Vite pipeline instead.
inline: [/markstream-vue/],
},
},
},
});

View file

@ -0,0 +1,112 @@
# 流式代码块高亮丢失:根因实锤与确定性修复报告
日期2026-07-29 结论状态:**已实锤(源码级 + 受控实验双重验证)**
## 摘要
流式输出中的代码块高亮「偶现有、偶现无,定居后也可能永久纯文本」,根因在 **markstream-vue@1.0.5**`MarkdownCodeBlockNode` 组件(`dist/index8.js`
1. `De()` 无参调用时**永远不会断开上一个 MutationObserver**(短路 bug流式期间每个内容 chunk 泄漏一个 observer
2. 泄漏的过期 observer 在新 DOM 写入时抢先执行,把当前渲染周期的「待绘制键」`se` 清成 `null`
3. 当前 observer 因此拿不到 `se`,无法把纯文本兜底切换回高亮 DOM代码块永久停在纯文本。
**修复是一行**:让 `De()` 无参调用真正断开当前 observer。打补丁后同一受控实验从「永久纯文本」变为「稳定上色并保持」静态渲染无回归。
与 app 侧无关:`Markdown.vue` 的渲染计划(实测该消息判为 `shiki`、CSP、WASM、打包产物、`app://` 协议、0.0.12 的全部 markdown 改动均已逐项排除。
## 症状与影响面
- 流式进行中的代码块:第一个 chunk 上色一次后,后续 chunk 到达即退回纯文本兜底,直到流式结束仍是纯文本(即截图状态)。
- 偶现「有」首次上屏时机、rAF 落盘与 nextTick 的相对时序、observer 回调先后,三路随机变量叠加。
- 历史会话(重新挂载的静态块)**不受影响**——首次绘制走的是另一条必然成功的路径,这也解释了为何 bug 主要在流式期间被感知。
## 根因(源码级)
以下标识符均为 `markstream-vue@1.0.5` 压缩产物 `dist/index8.js` 中的真实符号。
### 设计机制
组件挂载即渲染纯文本兜底(`Fe()` 输出 `<pre class="shiki shiki-fallback">`),然后异步增强:
- 每个内容更新触发 watch → `Fe(u)`(显示兜底)→ `je(epoch, n)`(在渲染容器上装一个 MutationObserver等待 shiki-stream 在 rAF 中落盘高亮 DOM`kn()``updateCode``Ee()` 暂存待绘制键 `se = key`
- rAF 落盘触发 observer 回调:`ue+=1; const t=se; if(t) return cn(e) ? paint(se) : (se=null, De(l))`——epoch 当前则 `Ne(t)` 绘制(`le=''``oe=true`,高亮 DOM 取代兜底epoch 过期则清理。
- `Ee` 自身的绘制条件 `a && (!ie || r || i || ie===t)` 只在**首次**`ie` 为空)必然成立;首次之后的重绘**只能**走 MutationObserver 回调(`i` 依赖 rAF 落盘早于 nextTick是竞态不可靠
### 缺陷 A决定性`De()` 无参不断开 observer
```js
function De(e){null==e||e.disconnect(),e&&re!==e||(re=void 0)}
```
- 调用 `De()`(无参,本意「断开上一个 observer」`null==e` 为 true → **短路,`e.disconnect()` 永不执行**;仅把引用 `re` 置空。
- 于是每个流式 chunk 都在渲染容器上**多泄漏一个存活 observer**,且全部带 `subtree:true` 持续回调。
- 泄漏的过期 observer 回调命中 `se` 已暂存的时刻,走过期分支 `se=null, De(l)`——**把当前周期的待绘制键清掉**;当前 observer 随后回调拿到 `se===null`,放弃绘制。兜底永久驻留。
### 运行时证据(探针日志,修复前)
每个 chunk 周期稳定复现epoch 8 窗口):
```
[watch] epoch=8 len=126 Ie=true
[je] install epoch=8 ue=10
[Ee] a=true r=false i=false ie==t:false ← Ee 不绘制
[MO] fire se=set ue=10 n=4 cn=false ← 过期 observer(n=4) 抢跑
[MO] fire se=null ue=11 n=7 cn=false ← 过期 observer(n=7)
[MO] fire se=null ue=12 n=10 cn=true ← 当前 observerse 已被清,放弃
```
- 同一窗口内 3 个 observer 实例同时存活(`n=4/7/10` 分属 epoch 6/7/8 安装的实例)——直接证明 `De()` 泄漏。
- `updateCode` 每次都正常 RESOLVED无挂起、无抛错、无任何 console 告警);高亮 DOM 在后台持续更新(采样末期 302 个 token span、样式表 762 字符均在),只是被兜底层遮住。
## 实验
### 环境
`/tmp/hl-probe`隔离环境未动仓库代码markstream-vue@1.0.5 / stream-markdown@0.0.16 / shiki@4.3.1 / vue@3.5.39(与线上 lockfile 完全一致Vite + 本机 Chrome headlessplaywright-core`MarkdownRender` 的 props 逐项复刻 `packages/web-markdown/src/Markdown.vue``mode:chat``code-renderer:shiki`、github 双主题、`code-block-props``loading:false``smooth-streaming``defer-nodes-until-visible:false`)。以 50ms 间隔采样 `data-markstream-enhancement-state`、token span 数、共享样式表长度、兜底 pre 可见性。
### 结果A/B
| 场景 | 修复前 | 修复后 |
|---|---|---|
| S1 冷启动 + 36 chunk × 100ms | 约 1s 上色一次后退回纯文本;**流式结束及 +9s 仍 pending + 兜底可见**= 线上截图) | 989ms 上色;流式结束 `ready`+9s 保持 |
| S2 冷启动 + 36 chunk × 800ms | 同样永久纯文本排除「chunk 间隔竞态」假设) | 1570ms 上色;结束 `ready`;保持 |
| S0 静态一次性渲染 | `ready`97 span正常 | `ready`,无回归 |
### 修复补丁(验证用)
```diff
- function De(e){null==e||e.disconnect(),e&&re!==e||(re=void 0)}
+ function De(e){const t=e||re;t&&t.disconnect(),(!e||re===e)&&(re=void 0)}
```
语义对照:显式 `De(l)`(回调内自用)行为不变;`De()` 无参现在正确断开 `re` 指向的当前 observer——过期 observer 不再存活,无法清 `se`,当前 observer 在每次 rAF 落盘后稳定完成绘制。
## 为什么「偶现有偶现无」
- **有**:首次绘制(`Ee``!ie` 分支)必然成功一次;若随后某次 rAF 落盘恰好早于 `Ee` 的 nextTick 检查(`i=true`),或当前 observer 抢在过期 observer 之前回调,就能再上色一次。三个事件同帧竞争,结果随机。
- **无**:一旦过期 observer 抢先(概率随泄漏数量逐 chunk 增大),`se` 被清该周期及之后全部周期都只能停留在兜底——流越长、chunk 越多,越接近必然无。
- 定居(流式结束)后无新 mutation失效状态自我锁定即「截图里定居后仍纯文本」。
## 确定性修复方案
**方案一推荐pnpm patch 锁定修复**
```bash
pnpm patch markstream-vue@1.0.5
# 编辑临时目录中的 dist/index8.js应用上面的 De() 补丁
pnpm patch-commit <临时目录>
```
补丁作用于安装产物,随 `pnpm install` 自动应用CI/打包链路无需改动。注意 desktop 与 web 共用同一 lockfile 依赖,单点修复两端生效。
**方案二:上游修复**
向 markstream-vue 提 issue/PR附本报告的机制说明与复现待发版后 bump 依赖。周期长,建议先用方案一落地,上游发版后移除 patch。
**不建议的绕道**:定居时强制重挂代码块(不治流式期间)、预热 shiki只影响首次上屏时机不修本 bug、改 `code-renderer`(因噎废食)。
## 备注
- 修复后流式中 `pending→ready` 会逐 chunk 抖动一次(`Fe` 同步显示兜底、observer 同帧内切回),肉眼基本不可感;若要彻底消除需改 markstream 的兜底策略,不在本次范围。
- 早期静态分析阶段曾怀疑「epoch 中止竞态 / shiki 冷加载预热」S2 慢 chunk 实验将其证伪800ms 间隔仍必现),最终根因以本报告实验证据为准。
- 探针环境保留在 `/tmp/hl-probe`(含 instrumentation 补丁与 runner 脚本),可复现本报告全部数据;仓库代码未做任何改动。

View file

@ -12,8 +12,11 @@
"@moonshot-ai/web-i18n": "workspace:*",
"@moonshot-ai/web-ui": "workspace:*",
"katex": "^0.17.0",
"markstream-vue": "^1.0.4",
"stream-markdown": "^0.0.16"
"markstream-vue": "^1.0.7",
"monaco-editor": "^0.55.1",
"stream-diffs": "^0.0.2",
"stream-markdown": "^0.0.16",
"stream-monaco": "^0.0.49"
},
"peerDependencies": {
"vue": "^3.5.35",

View file

@ -362,13 +362,20 @@ const CODE_DARK_THEME = 'github-dark';
// `loading: false` is the important one. markstream's CodeBlock shows a loading
// SKELETON whenever `!stream && loading`, and its `loading` prop DEFAULTS TO
// TRUE. We never set it, so every settled (non-streaming) code block sat in the
// skeleton state until shiki finished highlighting it and when a screenful of
// code mounts at once (switching to a long session, or a fast burst of output)
// shiki can't keep up, so the skeletons get stuck and the whole page reads as
// blank placeholders. Pinning `loading` to false drops the skeleton entirely:
// the block renders its plain-text fallback immediately and shiki upgrades it to
// the highlighted version when the highlighter is ready. Streaming blocks are
// unaffected (their `stream` is true, so the skeleton gate was already false).
// skeleton state until the highlighter finished and when a screenful of code
// mounts at once (switching to a long session, or a fast burst of output) the
// highlighter can't keep up, so the skeletons get stuck and the whole page
// reads as blank placeholders. Pinning `loading` to false drops the skeleton
// entirely: the block renders its plain-text fallback immediately and upgrades
// to the highlighted version when the highlighter is ready. Streaming blocks
// are unaffected (their `stream` is true, so the skeleton gate was already
// false).
// Chat code blocks show no gutter: line numbers eat 3+ characters of reading
// width and every chat block starts at line 1 anyway. stream-diffs derives its
// gutter from `lineNumbers === false` (boolean, not monaco's 'off' string).
// This rides inside codeBlockProps because markstream 1.0.7 only forwards the
// top-level codeBlockMonacoOptions to the 'monaco' renderer kind the 'shiki'
// kind's props object omits it, while codeBlockProps reach the same component.
const codeBlockProps = {
showHeader: true,
showCopyButton: true,
@ -377,6 +384,7 @@ const codeBlockProps = {
showCollapseButton: false,
showFontSizeButtons: false,
loading: false,
monacoOptions: { lineNumbers: false },
};
function copyCodeBlockFallback(code: string): void {
@ -389,11 +397,12 @@ function copyCodeBlockFallback(code: string): void {
}
// Root cause for the "large session turns into code skeletons" failure:
// markstream mounts every code block in the loaded transcript, then shiki has
// to tokenize all of them. `loading: false` removes the visible skeleton gate,
// but it still leaves a long shiki queue on very large messages. Heavy messages
// therefore use markstream's plain <pre> renderer: no highlighter queue, no
// skeleton path, and the content remains immediately readable.
// markstream mounts every code block in the loaded transcript, then the
// highlighter has to tokenize all of them. `loading: false` removes the
// visible skeleton gate, but it still leaves a long highlighter queue on very
// large messages. Heavy messages therefore use markstream's plain <pre>
// renderer: no highlighter queue, no skeleton path, and the content remains
// immediately readable.
// ---------------------------------------------------------------------------
// ```diff fences are handled locally, NOT by markstream.
@ -471,7 +480,7 @@ function copyDiff(code: string, idx: number) {
<template>
<div ref="mdRef" class="md">
<template v-for="(seg, i) in segments" :key="i">
<!-- Non-diff markdown markstream (smooth streaming + shiki) -->
<!-- Non-diff markdown markstream (smooth streaming + monaco code blocks) -->
<MarkdownRender
v-if="seg.kind === 'md'"
:content="seg.text"

257
pnpm-lock.yaml generated
View file

@ -126,8 +126,8 @@ importers:
specifier: ^0.17.0
version: 0.17.0
markstream-vue:
specifier: ^1.0.4
version: 1.0.5(katex@0.17.0)(mermaid@11.16.0)(stream-markdown@0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2)))(vue-i18n@11.4.6(vue@3.5.39(typescript@6.0.2)))(vue@3.5.39(typescript@6.0.2))
specifier: ^1.0.7
version: 1.0.7(katex@0.17.0)(mermaid@11.16.0)(stream-diffs@0.0.2(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.39(typescript@6.0.2)))(stream-markdown@0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2)))(stream-monaco@0.0.49(monaco-editor@0.55.1))(vue-i18n@11.4.6(vue@3.5.39(typescript@6.0.2)))(vue@3.5.39(typescript@6.0.2))
mermaid:
specifier: ^11.15.0
version: 11.16.0
@ -201,8 +201,8 @@ importers:
specifier: ^0.17.0
version: 0.17.0
markstream-vue:
specifier: ^1.0.4
version: 1.0.5(katex@0.17.0)(mermaid@11.16.0)(stream-markdown@0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2)))(vue-i18n@11.4.6(vue@3.5.39(typescript@6.0.2)))(vue@3.5.39(typescript@6.0.2))
specifier: ^1.0.7
version: 1.0.7(katex@0.17.0)(mermaid@11.16.0)(stream-diffs@0.0.2(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.39(typescript@6.0.2)))(stream-markdown@0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2)))(stream-monaco@0.0.49(monaco-editor@0.55.1))(vue-i18n@11.4.6(vue@3.5.39(typescript@6.0.2)))(vue@3.5.39(typescript@6.0.2))
mermaid:
specifier: ^11.15.0
version: 11.16.0
@ -577,9 +577,6 @@ importers:
'@moonshot-ai/kimi-code-oauth':
specifier: workspace:^
version: link:../oauth
'@moonshot-ai/minidb':
specifier: workspace:*
version: link:../minidb
'@moonshot-ai/transcript':
specifier: workspace:^
version: link:../transcript
@ -698,18 +695,12 @@ importers:
'@moonshot-ai/agent-core':
specifier: workspace:^
version: link:../agent-core
'@moonshot-ai/agent-core-v2':
specifier: workspace:^
version: link:../agent-core-v2
'@moonshot-ai/kaos':
specifier: workspace:^
version: link:../kaos
'@moonshot-ai/kimi-code-oauth':
specifier: workspace:^
version: link:../oauth
'@moonshot-ai/klient':
specifier: workspace:^
version: link:../klient
'@moonshot-ai/kosong':
specifier: workspace:^
version: link:../kosong
@ -843,11 +834,20 @@ importers:
specifier: ^0.17.0
version: 0.17.0
markstream-vue:
specifier: ^1.0.4
version: 1.0.5(katex@0.17.0)(mermaid@11.16.0)(stream-markdown@0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2)))(vue-i18n@11.4.6(vue@3.5.39(typescript@6.0.2)))(vue@3.5.39(typescript@6.0.2))
specifier: ^1.0.7
version: 1.0.7(katex@0.17.0)(mermaid@11.16.0)(stream-diffs@0.0.2(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.39(typescript@6.0.2)))(stream-markdown@0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2)))(stream-monaco@0.0.49(monaco-editor@0.55.1))(vue-i18n@11.4.6(vue@3.5.39(typescript@6.0.2)))(vue@3.5.39(typescript@6.0.2))
monaco-editor:
specifier: ^0.55.1
version: 0.55.1
stream-diffs:
specifier: ^0.0.2
version: 0.0.2(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.39(typescript@6.0.2))
stream-markdown:
specifier: ^0.0.16
version: 0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2))
stream-monaco:
specifier: ^0.0.49
version: 0.0.49(monaco-editor@0.55.1)
devDependencies:
typescript:
specifier: 6.0.2
@ -1492,14 +1492,14 @@ packages:
'@fastify/swagger@9.8.0':
resolution: {integrity: sha512-GdRkUboXu++nChrmWM22SNDkabB529TL7cQ4RDsPDP76ro1kSW1cLIEZ9S8ZUbJKYUciHgLAgiX8SHzCnqZdnQ==}
'@floating-ui/core@1.7.5':
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
'@floating-ui/core@1.8.0':
resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
'@floating-ui/dom@1.7.6':
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
'@floating-ui/dom@1.8.0':
resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
'@floating-ui/utils@0.2.11':
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
'@floating-ui/utils@0.2.12':
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
'@fontsource-variable/jetbrains-mono@5.2.8':
resolution: {integrity: sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q==}
@ -2079,6 +2079,36 @@ packages:
resolution: {integrity: sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==}
engines: {node: '>=14.18.0'}
'@pierre/diffs@1.2.12':
resolution: {integrity: sha512-pY/gmgWL03WnagqCyCnBi3QtRXUv4hCIY6FYqd5b1ZGaoI6a4Bsji8j+yRl2RfzPh/8Hf19rCl1GE80G6a1cLQ==}
peerDependencies:
react: ^18.3.1 || ^19.0.0
react-dom: ^18.3.1 || ^19.0.0
'@pierre/theme@1.1.0':
resolution: {integrity: sha512-GC2OWTAfTIIWWYhPCygwG8t2EtePQkRfON4MI2rwIkJylmiyqIttJID2dCL8sUD8cNdEvYkEyfEHHKMeCiDLoQ==}
engines: {vscode: ^1.0.0}
'@pierre/theming@0.0.2':
resolution: {integrity: sha512-QM1M4stXfnzfaE8I8YbjXSApV8c+2dBsXJj8eYg9WTpBR/cTmCZIcfGnN4p13iRrYu2Br/R/OJfEL7uR8Qjctw==}
peerDependencies:
'@pierre/theme': ^1.1.0
'@shikijs/themes': ^3.0.0 || ^4.0.0
react: ^18.3.1 || ^19.0.0
react-dom: ^18.3.1 || ^19.0.0
shiki: ^3.0.0 || ^4.0.0
peerDependenciesMeta:
'@pierre/theme':
optional: true
'@shikijs/themes':
optional: true
react:
optional: true
react-dom:
optional: true
shiki:
optional: true
'@pinojs/redact@0.4.0':
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
@ -2361,26 +2391,45 @@ packages:
resolution: {integrity: sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==}
engines: {node: '>=20'}
'@shikijs/engine-javascript@3.23.0':
resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==}
'@shikijs/engine-javascript@4.3.1':
resolution: {integrity: sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==}
engines: {node: '>=20'}
'@shikijs/engine-oniguruma@3.23.0':
resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==}
'@shikijs/engine-oniguruma@4.3.1':
resolution: {integrity: sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==}
engines: {node: '>=20'}
'@shikijs/langs@3.23.0':
resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==}
'@shikijs/langs@4.3.1':
resolution: {integrity: sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==}
engines: {node: '>=20'}
'@shikijs/monaco@3.23.0':
resolution: {integrity: sha512-OCApTdAGTHMFUXSYwGztW6EnlxXsWNrpnGf+uO+AznE+khC6V1/8QjuJESIcvZUIq9iAp4ZCNYosZKSVj1Hctg==}
'@shikijs/primitive@4.3.1':
resolution: {integrity: sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==}
engines: {node: '>=20'}
'@shikijs/themes@3.23.0':
resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==}
'@shikijs/themes@4.3.1':
resolution: {integrity: sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==}
engines: {node: '>=20'}
'@shikijs/transformers@4.3.1':
resolution: {integrity: sha512-z6ir0bGDgWcF2FduktEfPgIsdOtIlDiLAjFBgBzE42Q9xHbkkIXZtORHzlLVB71iZP9elEcqKg6keajvOUwE2A==}
engines: {node: '>=20'}
'@shikijs/types@3.23.0':
resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==}
@ -2811,6 +2860,9 @@ packages:
ajv@8.20.0:
resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
alien-signals@2.0.8:
resolution: {integrity: sha512-844G1VLkk0Pe2SJjY0J8vp8ADI73IM4KliNu2OGlYzWpO28NexEUvjHTcFjFX3VXoiUtwTbHxLNI9ImkcoBqzA==}
alien-signals@3.2.1:
resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==}
@ -3480,6 +3532,9 @@ packages:
resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==}
engines: {node: '>=20.19.0'}
dompurify@3.2.7:
resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
dompurify@3.4.11:
resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==}
@ -4440,6 +4495,9 @@ packages:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
lru_map@0.4.1:
resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@ -4468,6 +4526,11 @@ packages:
resolution: {integrity: sha512-FmCjj7D0C54EhWNn6WdSIBoyEbEoLeqE/Orgzik+CHSIz3aQJW3/U3uc8BEOMdGl/SE4O+s4BjwKhtO7LPOwWA==}
engines: {node: '>=18'}
marked@14.0.0:
resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
engines: {node: '>= 18'}
hasBin: true
marked@16.4.2:
resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
engines: {node: '>= 20'}
@ -4481,15 +4544,16 @@ packages:
markstream-core@1.0.3:
resolution: {integrity: sha512-QXn+yERo1q+RD6YlGDW61zc/af65uhkQEH3K8YvEKkprHbgRJ/JIeBeEQjELCTyBnPoxqtKQ7I565rylY+PePg==}
markstream-vue@1.0.5:
resolution: {integrity: sha512-b8C0/oRB54OcZfMNeJXImqD2DTkJPtz2frhsBzq5l4cycCyaBWUc8cMxDCOIRL1KSyjxEmz8kxRg4viy3OPxgg==}
markstream-vue@1.0.7:
resolution: {integrity: sha512-88UVoAH34jh/BXGUVYtK8t+Tw9JETbHhRVF2DwBAIE8r8W6MO27xRADotHpz1Qn34Mas6XRW6NmqvzwwjtvciQ==}
peerDependencies:
'@antv/infographic': ^0.2.3
'@terrastruct/d2': '>=0.1.33'
katex: '>=0.16.22'
mermaid: '>=11'
stream-diffs: '>=0.0.2'
stream-markdown: '>=0.0.16'
stream-monaco: '>=0.0.45'
stream-monaco: '>=0.0.48'
vue: '>=3.0.0'
vue-i18n: '>=9'
peerDependenciesMeta:
@ -4501,6 +4565,8 @@ packages:
optional: true
mermaid:
optional: true
stream-diffs:
optional: true
stream-markdown:
optional: true
stream-monaco:
@ -4623,6 +4689,9 @@ packages:
mlly@1.8.2:
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
monaco-editor@0.55.1:
resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@ -5336,6 +5405,9 @@ packages:
vue:
optional: true
shiki@3.23.0:
resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==}
shiki@4.3.1:
resolution: {integrity: sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==}
engines: {node: '>=20'}
@ -5448,14 +5520,28 @@ packages:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
stream-markdown-parser@1.0.9:
resolution: {integrity: sha512-uqf2F7DMBxc2JCWv9QyrzFNWQpzLF8zIH0BuLgA5LKv2jRbF+VJ9Dqz7LyRToJilAnns7PTud5XbsaSmIcjZ6Q==}
stream-diffs@0.0.2:
resolution: {integrity: sha512-mYH+kBCmrN2ebVPNkvQIFUSPemFxUPPPk+NT4H/wEzZ9NCt5Eaa8OE05f/1MA5HofZ64LYbTOliQTU6VghHeVA==}
peerDependencies:
vue: ^3.3.0
peerDependenciesMeta:
vue:
optional: true
stream-markdown-parser@1.1.4:
resolution: {integrity: sha512-fjkHUh7uIOH5Yp/kt26l2td+q/4sRusMFO4iB0d+GcWj40ZZuH+HxxiLxGeSD1oo8YdyapokzHOfchDIYFnNgg==}
stream-markdown@0.0.16:
resolution: {integrity: sha512-2WoOxlpc3N5RLc3zGuW+g/w76z6ketWBY0N1YzDYbXds1qw7zrUBv5PTQ3DOtpqgCjLuF3P2iCfjJTEqWGv0NQ==}
peerDependencies:
shiki: '>=3.23.0'
stream-monaco@0.0.49:
resolution: {integrity: sha512-ksZYJXw45NralnpgWqcptqrgDdbGZaTyYmvATvhlK8eY0tyeTxs0iuQzMTF4cpGf5OlIgkNJOfbFh4M97OozSw==}
hasBin: true
peerDependencies:
monaco-editor: '>=0.52.2 <0.56.0'
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@ -6766,16 +6852,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@floating-ui/core@1.7.5':
'@floating-ui/core@1.8.0':
dependencies:
'@floating-ui/utils': 0.2.11
'@floating-ui/utils': 0.2.12
'@floating-ui/dom@1.7.6':
'@floating-ui/dom@1.8.0':
dependencies:
'@floating-ui/core': 1.7.5
'@floating-ui/utils': 0.2.11
'@floating-ui/core': 1.8.0
'@floating-ui/utils': 0.2.12
'@floating-ui/utils@0.2.11': {}
'@floating-ui/utils@0.2.12': {}
'@fontsource-variable/jetbrains-mono@5.2.8': {}
@ -7372,6 +7458,30 @@ snapshots:
tslib: 2.8.1
webcrypto-core: 1.9.2
'@pierre/diffs@1.2.12(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
dependencies:
'@pierre/theme': 1.1.0
'@pierre/theming': 0.0.2(@pierre/theme@1.1.0)(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(shiki@4.3.1)
'@shikijs/transformers': 4.3.1
diff: 9.0.0
hast-util-to-html: 9.0.5
lru_map: 0.4.1
react: 19.2.7
react-dom: 19.2.7(react@19.2.7)
shiki: 4.3.1
transitivePeerDependencies:
- '@shikijs/themes'
'@pierre/theme@1.1.0': {}
'@pierre/theming@0.0.2(@pierre/theme@1.1.0)(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(shiki@4.3.1)':
optionalDependencies:
'@pierre/theme': 1.1.0
'@shikijs/themes': 4.3.1
react: 19.2.7
react-dom: 19.2.7(react@19.2.7)
shiki: 4.3.1
'@pinojs/redact@0.4.0': {}
'@protobufjs/aspromise@1.1.2': {}
@ -7543,31 +7653,61 @@ snapshots:
'@types/hast': 3.0.5
hast-util-to-html: 9.0.5
'@shikijs/engine-javascript@3.23.0':
dependencies:
'@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
oniguruma-to-es: 4.3.6
'@shikijs/engine-javascript@4.3.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/vscode-textmate': 10.0.2
oniguruma-to-es: 4.3.6
'@shikijs/engine-oniguruma@3.23.0':
dependencies:
'@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
'@shikijs/engine-oniguruma@4.3.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/vscode-textmate': 10.0.2
'@shikijs/langs@3.23.0':
dependencies:
'@shikijs/types': 3.23.0
'@shikijs/langs@4.3.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/monaco@3.23.0':
dependencies:
'@shikijs/core': 3.23.0
'@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
'@shikijs/primitive@4.3.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.5
'@shikijs/themes@3.23.0':
dependencies:
'@shikijs/types': 3.23.0
'@shikijs/themes@4.3.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/transformers@4.3.1':
dependencies:
'@shikijs/core': 4.3.1
'@shikijs/types': 4.3.1
'@shikijs/types@3.23.0':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
@ -8104,6 +8244,8 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
alien-signals@2.0.8: {}
alien-signals@3.2.1: {}
ansi-colors@4.1.3: {}
@ -8808,6 +8950,10 @@ snapshots:
dependencies:
domelementtype: 3.0.0
dompurify@3.2.7:
optionalDependencies:
'@types/trusted-types': 2.0.7
dompurify@3.4.11:
optionalDependencies:
'@types/trusted-types': 2.0.7
@ -9997,6 +10143,8 @@ snapshots:
dependencies:
yallist: 4.0.0
lru_map@0.4.1: {}
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@ -10025,23 +10173,27 @@ snapshots:
punycode.js: 2.3.1
uc.micro: 2.1.0
marked@14.0.0: {}
marked@16.4.2: {}
marked@18.0.5: {}
markstream-core@1.0.3: {}
markstream-vue@1.0.5(katex@0.17.0)(mermaid@11.16.0)(stream-markdown@0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2)))(vue-i18n@11.4.6(vue@3.5.39(typescript@6.0.2)))(vue@3.5.39(typescript@6.0.2)):
markstream-vue@1.0.7(katex@0.17.0)(mermaid@11.16.0)(stream-diffs@0.0.2(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.39(typescript@6.0.2)))(stream-markdown@0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2)))(stream-monaco@0.0.49(monaco-editor@0.55.1))(vue-i18n@11.4.6(vue@3.5.39(typescript@6.0.2)))(vue@3.5.39(typescript@6.0.2)):
dependencies:
'@chenglou/pretext': 0.0.8
'@floating-ui/dom': 1.7.6
'@floating-ui/dom': 1.8.0
markstream-core: 1.0.3
stream-markdown-parser: 1.0.9
stream-markdown-parser: 1.1.4
vue: 3.5.39(typescript@6.0.2)
optionalDependencies:
katex: 0.17.0
mermaid: 11.16.0
stream-diffs: 0.0.2(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.39(typescript@6.0.2))
stream-markdown: 0.0.16(react@19.2.7)(shiki@4.3.1)(vue@3.5.39(typescript@6.0.2))
stream-monaco: 0.0.49(monaco-editor@0.55.1)
vue-i18n: 11.4.6(vue@3.5.39(typescript@6.0.2))
matcher@3.0.0:
@ -10172,6 +10324,11 @@ snapshots:
pkg-types: 1.3.1
ufo: 1.6.4
monaco-editor@0.55.1:
dependencies:
dompurify: 3.2.7
marked: 14.0.0
mri@1.2.0: {}
ms@2.1.3: {}
@ -10945,6 +11102,17 @@ snapshots:
react: 19.2.7
vue: 3.5.39(typescript@6.0.2)
shiki@3.23.0:
dependencies:
'@shikijs/core': 3.23.0
'@shikijs/engine-javascript': 3.23.0
'@shikijs/engine-oniguruma': 3.23.0
'@shikijs/langs': 3.23.0
'@shikijs/themes': 3.23.0
'@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.5
shiki@4.3.1:
dependencies:
'@shikijs/core': 4.3.1
@ -11064,7 +11232,17 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
stream-markdown-parser@1.0.9:
stream-diffs@0.0.2(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.39(typescript@6.0.2)):
dependencies:
'@pierre/diffs': 1.2.12(@shikijs/themes@4.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
optionalDependencies:
vue: 3.5.39(typescript@6.0.2)
transitivePeerDependencies:
- '@shikijs/themes'
- react
- react-dom
stream-markdown-parser@1.1.4:
dependencies:
markdown-it-container: 4.0.0
markdown-it-footnote: 4.0.0
@ -11084,6 +11262,13 @@ snapshots:
- solid-js
- vue
stream-monaco@0.0.49(monaco-editor@0.55.1):
dependencies:
'@shikijs/monaco': 3.23.0
alien-signals: 2.0.8
monaco-editor: 0.55.1
shiki: 3.23.0
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0