1068 Commits

Author SHA1 Message Date
xfy
5e5583fe20 feat(katex): 注册 16 个物理学宏表
katex-rs 默认 Settings 无物理宏表,\vu \dv \dd \pdv \divg \curl \grad
\qty \RR \ZZ \NN \QQ \CC \bra \ket \braket \expval \abs \norm 渲染为红字
(实测正确页面 648 的 137 个公式中 48 处物理宏坏掉)。

- 新增 physics_macros() 表(MacroDefinition::StaticStr,参数自动推导)
- inline_settings/display_settings 构造后注入物理宏
- 刻意差异:\divg(散度)不复用 \div(除号),两者并存
- 覆写内置 \bra/\ket/\braket 为物理语义版本
- 6 个物理宏单测覆盖数集/微积分/Dirac 记号/向量/范数
2026-07-23 11:55:38 +08:00
xfy
68ac629f44 fix(sql-console): 写操作后全量失效相关缓存
SQL 控制台执行 UPDATE/DELETE 等 SQL 直改 DB 时绕过了 server function 的
正常缓存失效路径,导致 moka 单篇缓存(TTL 600s)与 SSR 磁盘缓存继续吐出
陈旧数据(含已删除文章),近 10 分钟的陈旧窗口。

- execute_sql 执行成功后,若含写语句则全量失效文章/搜索/评论缓存 +
  SSR 公开页磁盘缓存 + bump 全局世代号
- cache.rs 新增 invalidate_all_comments(评论列表 + 待审数)
- 抽 writes_affect_cache 纯函数 + 3 个单测覆盖写/只读/混合语句
2026-07-23 11:49:52 +08:00
xfy
2b75ac4d28 fix(editor): 修复 tiptap 序列化转义脚注语法 [^id] → \[^id\] 导致失效
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
@tiptap/markdown 的 escapeMarkdownSyntax (MarkdownManager.ts:1112) 对所有
非代码文本节点做 [→\[、]→\] 转义,导致脚注引用/定义 [^id] 被破坏为 \[^id\],
pulldown-cmark 将其视为字面文本而非脚注语法,渲染出纯文本而非上标链接。

与数学公式节点同类问题(LaTeX 通过 atom Node + renderMarkdown 绕过),
但脚注是普通文本节点无法走相同路径,故在 getMarkdown 输出后做后处理:
将 \[^label\] 还原为 [^label],仅匹配脚注专属模式不影响普通链接语法。

同时修复数据库中已损坏的 markdown-syntax-test 和 markdown-quan-te-xing-
ce-shi-wen-zhang 两篇文章的 content_md,并通过 rebuild 全量重建 content_html。
2026-07-23 11:10:21 +08:00
xfy
c693d82bab docs(agents): 重写 AGENTS.md 为完整的仓库指南
并行 scout 扫描 src/tests/configs/scripts/libs 五个区域,
综合整理为面向 AI 助手的 Repository Guidelines:架构与数据流、
关键目录、开发命令、配置与环境变量、代码约定、测试与 QA、CI。
保留原有 Workflow 与 JS 库说明,扩展为结构化文档。
2026-07-23 10:24:05 +08:00
xfy
68e290a9fe feat(write): 新建文章默认发布状态改为直接发布
将 /admin/write 新建模式的默认发布状态从「存为草稿」改为「直接发布」。
编辑模式仍回填文章实际状态,不受影响。
2026-07-23 10:10:51 +08:00
xfy
f7e4d2f5ea chore: release v0.5.0
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
数学公式(KaTeX)、mermaid 流程图、bun 代码运行器、文章脚注、Vue SFC 高亮等新功能;SSR 缓存失效根治、Docker 构建/部署修复、性能优化系列(mimalloc + 热点路径零拷贝/单遍扫描)。
v0.5.0
2026-07-22 17:41:29 +08:00
xfy
44c76b48c1 chore(deploy): 新增 xun 服务器全量部署脚本
scripts/xun.fish(fish + docker buildx)一键完成:
runner base / 主应用(透传 git 信息)/ 5 个 runner 子镜像的 amd64 构建、
架构校验、导出 scp 到 xun、导入去 localhost/ 前缀、滚动重启 app 容器,
并附容器/迁移/runner/healthz/外网 HTTPS 全链路校验。
2026-07-22 17:33:09 +08:00
xfy
856eeb758c fix(build): Docker 构建透传 git 信息,修复 x-yggdrasil-git 恒为 unknown
.dockerignore 排除 .git/,容器内 build.rs 跑不了 git 命令,导致
x-yggdrasil-git / git_hash / commit_date 响应头和启动日志全是 "unknown"
(version 来自 CARGO_PKG_VERSION 不受影响)。

三级取值链(build.rs env_or_git):
  YGG_BUILD_* 环境变量 → 本地 git 命令 → "unknown"
第一级 std::env::var 优先读 docker --build-arg 注入的值,第二级保留
本地 cargo build 的既有行为,兜底不变。

- Dockerfile: 主应用 server build 前加 ARG+ENV 块(ARG 仅 RUN 可见,
  build.rs 由 cargo 调用非 RUN,故需 export 成 ENV 让 cargo build 继承)
- Makefile: GIT_DESCRIBE/HASH/DATE 在宿主采集,GIT_BUILD_ARGS 复用块
  注入 docker/docker-amd64/docker-multiarch 三个 target
- deploy-to-linux skill: 两处 docker buildx build 命令(首装 + 更新)
  加同名 --build-arg,附说明
2026-07-22 16:05:49 +08:00
xfy
eaabb87108 docs(code-runner): AGENTS.md 补充别名归一化与 bun 镜像说明
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
Code Runner API 层描述补充 normalize_lang 别名归一化机制:
- parse_fence_info 在 markdown 渲染期把别名(js→node, ts→bun 等)换成 canonical
- start_exec/start_exec_stream 在 LANGUAGES.get 前显式 normalize_lang

Runner images 段落补 yggdrasil-runner-bun,记录三个踩坑点:
- bun 不在 Alpine 3.18 仓库,用 bun.sh 脚本(自动选 musl 变体)
- bun 的 musl 二进制是 C++,需 libstdc++ + libgcc 运行时
- BUN_INSTALL 必须 export,否则 curl|bash 管道右侧读不到
2026-07-21 15:47:39 +08:00
xfy
ca676a6146 feat(docker): 新增 yggdrasil-runner-bun 沙箱镜像
bun 在 Alpine 3.18 官方仓库不可用,用 bun.sh 安装脚本(多架构、自动检测
/etc/alpine-release 选 musl 变体)。踩坑三处:

1. BUN_INSTALL 必须对管道两侧生效——「VAR=x cmd | bash」只给左侧设变量,
   bash 侧读不到会 fallback 到 $HOME/.bun。改用 export。
2. bun 的 musl 二进制是 C++ 写的,依赖 libstdc++ + libgcc(musl libc 不带
   C++ 运行时),缺失时报 _ZNSt18condition_variable... symbol not found。
   这两个是运行时依赖,apk del 不得删除。
3. 安装路径固定到 /opt/bun + symlink /usr/local/bin/bun,避免依赖 $HOME
   (runner 用户 $HOME 可能不可写)。

本地验证通过:
- bun --version → 1.3.14
- runner 用户(1000:1000) + 只读根 + tmpfs(/code mode=1777, /tmp exec) 下
  成功执行含 interface/类型注解/循环的 TypeScript 代码。

build-runners.sh 加 bun 构建步骤;deploy-to-linux SKILL.md 的 build 循环、
docker save 列表、docker tag 列表、verify 循环全部补 bun(5→6 个镜像)。
2026-07-21 15:44:38 +08:00
xfy
52e5ee2736 feat(admin): 代码试运行沙箱加 bun 语言按钮
SUPPORTED_LANGS 追加 'bun'(仅 canonical key,不展示别名以免按钮拥挤),
default_source 加 bun 分支——示例用 TypeScript 类型注解(:string / :number)
体现 bun 跑 TS 的语言特性。

aliases(js/ts/rs 等)经 normalize_lang 归一即命中此处某项,按钮无需重复。
2026-07-21 15:24:41 +08:00
xfy
864c813c6d feat(code-runner): 高亮层补 bun 别名、CodeMirror 加 TS 模式
syntect 高亮别名表(highlight.rs)加 ('bun','ts'):bun 运行器跑的是
TypeScript,归一化后用 ts 扩展名命中 TypeScript.sublime-syntax。

CodeMirror buildLanguageExtension(codemirror-editor/editor.ts)新增
bun/typescript/ts 分支 → javascript({ typescript: true }),让 bun 代码块
在阅读器侧编辑器获得 TS 类型感知高亮。归一化后 CodeRunner 实际只传
canonical key,但保留别名分支作防御(CodeMirror 也在 SQL 控制台等场景用)。

两边都补了测试:highlight_code_resolves_bun_alias_to_typescript 锁定
'bun' 与 'ts' 输出一致;CodeMirror 经 tsc --noEmit + vite build 通过。
2026-07-21 15:23:47 +08:00
xfy
22d226d4e8 feat(code-runner): 添加语言别名归一化与 bun 运行器注册
新增 normalize_lang 别名表,把读者/作者习惯的语言简写归一到 canonical key:
- js / javascript → node
- rs              → rust
- ts / typescript → bun

归一化在 markdown 渲染期(parse_fence_info)即完成,使 data-lang、
ExecRequest.language、ExecResult.language、LANGUAGES.get 全程只见到
canonical 名(python/node/go/rust/bun),避免前端用别名调 StartExec 时
查表落空。execute.rs 的 start_exec / start_exec_stream 在 spawn 前显式
normalize_lang,保证 ExecResult.language 回显 canonical。

同时注册 bun 语言(canonical):镜像 yggdrasil-runner-bun,run_cmd
'bun run /code/main.ts',默认资源与 node 对齐(256MB/5s)。Dockerfile
与 build-runners.sh 跟进。

测试覆盖三处协同契约:languages.rs 的 normalize_lang 别名表、
execute.rs 的 validate 接受别名、markdown.rs 的 data-lang 归一化。
2026-07-21 15:21:58 +08:00
xfy
460088fa43 docs(skill): deploy-to-linux 添加手动部署模式
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
用户指定服务器后,agent 只做第 0 步只读探测,然后按探测结果
输出填实占位符的完整部署命令清单(分本地/服务器两块),
用户自行复制执行。同步更新 description 触发关键词。
2026-07-21 14:13:49 +08:00
xfy
ccee9131a9 fix(highlight): 编译期内嵌自定义语法,修复线上 Docker 镜像代码高亮缺失
生产镜像是 FROM scratch 的静态 musl 二进制,运行时不存在 syntaxes/ 目录;
CARGO_MANIFEST_DIR 烘焙的是构建机路径(Docker 内为 /build),运行时
add_from_folder 必然失败,Kotlin/Swift/TypeScript/TSX/JSX/Zig/Vue 七种
语言静默回退为纯文本。

改为 include_str! 编译期嵌入 .sublime-syntax,彻底消除运行时文件依赖;
新增两个守护测试:内嵌列表与 syntaxes/ 目录一致性、自定义语法均已加载。
2026-07-21 14:10:38 +08:00
xfy
9e9c6be5ce docs(skill): deploy-to-linux 删除已修复的 Code Runner 限制、fish 兼容性改写为正向清单
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
- 删掉 'Code Runner 限制(已知)' 整节,Podman tmpfs 表只留客观差异不再
  '→ 影响 Code Runner,见末尾',因为 tmpfs uid/gid 问题已修复。
- 第 0 步决策表 $SHELL 一行措辞调整:fish 从 '陷阱' 改为 '兼容性提醒'。
- 'fish shell 陷阱' 章节重写为 'fish shell 兼容性':
  * 逐条核对所有 ssh <host> '...' 命令(; && || | 2>&1 docker {{}} fish 都
    原生支持, VAR= for...do...done $? 全部已规避)。
  * 给出 9 条语法的 bash/fish 对照表,标注哪些本文已用、哪些已避免。
  * 明确原则:需要 $()/VAR=/for...done 的逻辑放本地 zsh/bash。
2026-07-21 13:33:40 +08:00
xfy
d44b1565de docs(markdown): 新增 Markdown 全特性测试文章
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
覆盖 yggdrasil 渲染管线的全部特性:标题层级、强调、引用、嵌套列表、
任务列表、表格、自定义语法高亮(Kotlin/Swift/TS/TSX/Vue/Zig)、
KaTeX 行内与块级公式、Mermaid 各类图表、Runnable 代码块、
脚注、转义字符与多语言压力测试,便于回归验证整条渲染链路。
2026-07-21 11:26:38 +08:00
xfy
63a52f62c9 chore(format): mermaid-renderer 入口文件对齐 biome 格式
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
biome format 清理:去掉赋值语句的多余前导分号与空行。
该文件此前未走过 biome format(前导 ; 与 semicolons:always 配置无关,
仅为历史写法)。无行为变化。
2026-07-17 17:41:39 +08:00
xfy
5dceb39544 feat(editor): 数学公式节点带 KaTeX 预览,根治编辑器序列化破坏 LaTeX
问题:经 Tiptap 编辑器保存的数学公式,矩阵/分段函数等服务端 KaTeX 渲染失败。
根因:@tiptap/markdown 的 MarkdownManager.encodeTextForMarkdown 对所有非代码
文本节点做 escapeMarkdownSyntax + encodeHtmlEntities,把 LaTeX 的 \→\\、
_→\_、&→&amp;。编辑器内公式走普通文本路径,经 getMarkdown() 序列化后存库即损坏。

解法:把公式建模成 atom Node(InlineMath/DisplayMath),LaTeX 存 attrs.latex
而非子文本;声明 markdownTokenizer/parseMarkdown/renderMarkdown 顶层 spec 字段,
序列化时 renderMarkdown 直接拼 $...$/$$...$$,绕过 encodeTextForMarkdown
转义路径(MarkdownManager 有 handler 的非文本节点不走 escape)。

NodeView 内用客户端 katex.renderToString 渲染预览(类 Notion/Typora),双击编辑
LaTeX 源码。KaTeX CSS 已由 Dioxus.toml 全局注入,无需额外接线。

- 新增 katex ^0.16.22 依赖(workspace 已 hoisted)
- 11 个回归测试覆盖矩阵/分段函数/round-trip 序列化往返
2026-07-17 17:33:37 +08:00
xfy
e3e0829fcd fix(docker): 预装 binaryen 避免 dx 运行时下载 wasm-opt 失败
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
dx build @client --release 默认启用 wasm-opt 优化 WASM bundle。
镜像缺失 wasm-opt 时,dx 内部会从 GitHub Releases 动态下载,
而这个下载不受 Dockerfile 的 GH_PROXY 控制(GH_PROXY 只覆盖
dx/tailwindcss 那两个显式 curl 调用)。

后果:容器内 80 分钟超时后报
  Failed to create wasm-opt instance
  error decoding response body
  error reading a body from connection
  stream error received: unspecific protocol error detected

apt 装 binaryen 把 wasm-opt 放到 /usr/bin,dx 直接复用 PATH 里
现成的二进制,不再触发网络下载。仅影响 builder 阶段,
不进 runtime scratch 镜像。
2026-07-17 13:33:31 +08:00
xfy
156b51d697 feat(highlight): 文章页代码块支持 Vue SFC 语法高亮
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
阅读侧(syntect)此前对 ```vue 回退纯文本:syntect 默认集无 Vue
语法、syntaxes/ 无定义、别名表无 vue 条目。

官方 Vue Component.sublime-syntax 因 extends: Packages/HTML/...
用 Sublime Text 包路径,syntect 的 add_from_folder 解析不了(实测
panic),无法直接采用。改为自包含实现:

- syntaxes/Vue.sublime-syntax:自包含 Vue SFC 语法。<template>/
  <script>/<style> 三段在 main 入口按标签名分流(syntect context
  切换后无法回看标签名)。script 按 lang 属性经前瞻分流
  ts/tsx→source.ts、其余→source.js;style 统一 embed source.css
  (scss/less 无对应 scope,因 CSS 是超集兼容);template 内 {{ }}
  插值 embed source.js,其余走内置 HTML 语法。
- 关键约束(syntect 5.3 实测):embed 规则必须用纯净 match+embed+
  escape 写法,附加 pop/embed_scope 会破坏 embed 使其静默回退纯文本;
  escape 用 lookahead 不消费闭合标签。

编辑器侧(lowlight@3.3.0)与阅读侧是独立链路。lowlight 依赖的
highlight.js 11 早已移除 vue grammar,common/all 均不含;
highlightjs-vue 包停在 2019 年 1.0.0 且用旧版 hljsDefineVue 全局
API,与 lowlight@3 的 ESM register 不兼容。故编辑器侧用 registerAlias
把 vue 注册为 xml 别名兜底(template 段按 XML 着色,script/style 段
不精确但不抛 Unknown language);读者看到的阅读侧仍是完整高亮。

src/highlight.rs 别名表加 vue 条目(兜底大写 ```Vue 边界),新增 3 个
Vue 高亮单测(SFC 三段 / lang=ts 走 TS / 大小写别名)。
2026-07-17 11:42:55 +08:00
xfy
6fee07df1f feat(markdown): 文章页脚注完整支持(语义化 + back-link + 样式)
切换到 pulldown-cmark 的 GFM 脚注模式(与 GitHub 行为一致),并用自定义
事件渲染器替换默认输出,补齐语义化结构与无障碍属性:

- 解析:Options::all() 同时置 ENABLE_OLD_FOOTNOTES(bit 9|2),使
  has_gfm_footnotes() 返回 false 走 OLD 模式。remove(OLD) 会连
  ENABLE_FOOTNOTES 一起清掉(OLD 位掩码含 bit 2),故 remove 后须
  insert(ENABLE_FOOTNOTES) 单独加回。
- 渲染:事件循环新增 FootnoteReference / Start/End FootnoteDefinition
  三个分支。引用处 <sup class="fn-ref" id="fnref:{label}-{n}"> 带
  role="doc-noteref";定义处 <aside role="doc-footnote"
  aria-labelledby="..."> 取代默认 <div>;End 处按引用次数输出 N 个
  back-link(↩、↩²、↩³…)指向各自引用位置,role="doc-backlink"。
- 编号:按 label 首次出现顺序分配(1,2,3…),与定义位置无关。第一遍
  遍历顺带统计(复用现有两遍遍历模式,零额外扫描成本)。
- id 安全:footnote_id() 保留 label 原文(不转拼音),仅把 ASCII 标点
  /空格转 -,确保 ref↔def 双向一致且不破坏 HTML 属性。

sanitizer:放行 <section> 与 aria-labelledby;修复 is_safe_url 对
含冒号锚点(如 #fn:label)的误判——fragment 内冒号不是 scheme 分隔符,
# 开头的 URL 优先按锚点放行。

input.css:仿 blockquote 视觉语言(左侧边框 + 次级文本色)补脚注样式,
复用 paper 色彩变量,暗色模式自动适配。
2026-07-17 10:57:40 +08:00
xfy
3831d1af01 feat(header): 搜索入口改为图标按钮
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
将前台 Header 文本导航中的「搜索」项移除,改在 Header 右侧(主题切换按钮
左边)以放大镜图标 Link 呈现。样式与 ThemeToggle 对齐(圆形 padding、
currentColor 图标),适配明暗主题。

- nav.rs: 导航项列表去掉 Search,更新文档注释
- header.rs: 新增 SearchIconLink 组件(SVG 内联,fill=currentColor)
- frontend_layout.rs: right_content 在 ThemeToggle 前插入 SearchIconLink
2026-07-17 10:28:39 +08:00
xfy
fac247f60a feat(mermaid): 流程图配色对齐 Catppuccin + 容器美化
mermaid.initialize 之前用内置 'default'/'dark' 主题,配色与站点 Catppuccin
调色板完全脱节,图是"外来"的样子;且零 mermaid 专用 CSS,SVG 直接套在通用
pre 容器里,宽图横向滚动、无居中无留白。

改动:
- mermaid.ts:theme 切到 'base'(base 不硬编码颜色,themeVariables 能完全
  控制调色板;'default' 主题硬编码 mainBkg=#ECECFF 会阻断覆盖)。新增 Latte/
  Mocha 两套 themeVariables,节点用 surface 色阶、边框/连线用 subtext 色阶、
  文字用 primary text,hex 取自 themes/*.tmTheme。flowchart 配 basis 平滑曲线
  + diagramPadding 16 + useMaxWidth:true(配合 CSS 消除横向滚动)。
- input.css:新增 .md-content pre[data-mermaid-rendered] 规则,flex 居中 + svg
  max-width:100% 缩放适配;.mermaid-error 加 accent 色左边条提示。
- mermaid.test.ts:theme 断言同步到 'base'+darkMode;新增 themeVariables
  注入测试(钉住 Latte/Mocha 关键 hex)。

范围:纯前端 TS + CSS,零 Rust 改动。
2026-07-17 10:13:20 +08:00
xfy
302e05fe1f fix(docker): 补齐 Dockerfile 缺失的 katex-css 与 restore-webp 步骤
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
对齐 make build-linux 流水线,修两个同步遗漏:

- katex-css:镜像缺此步导致 $...$/$$...$$ 渲染成裸 span,无 KaTeX
  字体排版。
- restore-webp:dx 0.7.9 会把 public/*.webp 重编码成 VP8L 静图(动画帧
  丢失、体积反增 7-8×),需用源文件覆盖回 dx 产物目录后再拷贝。

两处均在注释中标注 keep in sync with make build-linux。
2026-07-16 18:16:35 +08:00
xfy
71211bc68d chore(docker): GitHub Releases 下载走 gh-proxy 镜像
dx tarball 与 tailwindcss 二进制都在 github.com releases 上,国内下载
~300KB/s 且频繁连接重置。新增 ARG GH_PROXY (默认 https://gh-proxy.com)
前缀到两处 GitHub 下载 URL;传空值可回退直连,便于非大陆环境构建。
2026-07-16 18:03:09 +08:00
xfy
ae1efda462 fix(build): build 前清除 static/ SSR 缓存目录
避免增量构建时残留的 SSR 缓存导致页面内容不更新。
与 make clean 中已有的 static/ 清理保持一致。
2026-07-16 17:39:44 +08:00
xfy
aac0c8198d fix(docker): 升级 builder 至 trixie 以满足 dx 对 GLIBC_2.39 的需求
dx v0.7.9 预编译二进制(both aarch64/x86_64-unknown-linux-gnu)依赖
GLIBC_2.39,而 Debian Bookworm (glibc 2.36) 不满足该要求。
将 builder 阶段基础镜像从 rust:1.96-bookworm 升级至
rust:1.96-trixie (Debian 13, glibc 2.41)。
2026-07-16 17:39:41 +08:00
xfy
0395c55213 fix(mermaid): 修复 tsc 类型错误
- mermaid.ts: initMermaid 早退路径(root 未命中)返回 Promise.resolve() 而非
  undefined,匹配 Promise<void> 签名。
- theme-transition.test.ts: resolveAsync 改用 mutable 容器 { fn: null } 包裹,
  绕过 TS 跨闭包控制流收窄(原 let + 闭包赋值,在 ?.() 处被判定为 null → never)。

上提交漏跑 tsc --noEmit(vitest 用 esbuild 不做类型检查),本次补验 tsc + vite build。
2026-07-16 17:06:06 +08:00
xfy
bdfc7a19e9 feat(mermaid): 流程图主题切换跟随 VT 圆形扩散动画
此前 mermaid.render() 异步,而 VT 回调同步,Dioxus effect 重跑发生在 VT 回调
之后的微任务,导致动画期间流程图不变、动画结束后才瞬切。

利用 startViewTransition 回调可返回 Promise 的特性(浏览器等其 resolve 才拍
NEW 快照、播动画),让 VT 回调等 mermaid 重渲染完成,新主题流程图进入快照,
圆形扩散覆盖整页含 mermaid 区域。

改动:
- theme-transition.ts: 新增 onThemeChange registry(回调可返回 Promise)。
  notifyThemeChange 遍历 registry 收集 Promise 返回聚合结果;THEME_CHANGE_EVENT
  事件保留(给 codemirror/xterm 同步换肤,向后兼容)。
  startThemeTransition 的 VT 回调改为 async,await notifyThemeChange 聚合 Promise,
  确保异步换肤组件(mermaid)完成后再拍 NEW 快照。
  applyResolvedTheme(跟随系统瞬切)不等 registry,保持瞬切语义。
- mermaid.ts: 顶层注册 onThemeChange 订阅,listener 调 rerenderExistingBlocks 并
  返回其 Promise,供 VT 回调等待。initMermaid/rerenderExistingBlocks 返回 Promise。
- post_content.rs: VT 期间(is-theme-transitioning)跳过 __initMermaid 调用,避免
  Dioxus effect 抢先重渲染穿透伪元素快照(照搬 code_runner/runner.rs 守卫);
  动画结束后 effect 重跑做幂等兜底。
- theme-transition.test.ts: VT callback 签名 void→Promise<void>,新增 registry
  等待测试 + 降级路径不等 registry 测试。
2026-07-16 16:58:27 +08:00
xfy
6eb7a743e5 fix(mermaid): 主题切换时重渲染已渲染的流程图
mermaid 把颜色烤进 SVG 内联样式,无法靠 CSS 原地切主题。此前 initMermaid
的幂等守卫一刀切跳过所有已渲染块,导致切主题后流程图停留在首次渲染的主题。

改动:
- initMermaid 改为双路径分发:未渲染块(<code>还在)走 IntersectionObserver
  懒加载;已渲染块(pre 内容已是 SVG)按 dataset.mermaidSource 缓存源码重渲染,
  同主题幂等跳过,主题变化才重渲染。
- 渲染逻辑抽到 renderBlock:用全局自增 renderCounter 生成唯一 id,避开 mermaid
  残留的 d-前缀布局节点冲突(#357)与同页 marker id 冲突(#5741);替换前清空
  pre.innerHTML 确保亮/暗两版 SVG 不并存。
- source 存进 dataset.mermaidSource 供重渲染回取(此时 <code> 已被 SVG 替换)。

触发机制不变:post_content.rs 的 use_effect 读 use_resolved_theme() 建订阅,
主题切换时重跑重调 __initMermaid 传入新 theme;mermaid.ts 据此决定是否重渲染。
2026-07-16 16:42:04 +08:00
xfy
f7a3336b76 style(details): 正文折叠块卡片化,自绘 chevron + hover/focus 态
正文手写的 <details>(区别于 .toc 导航块)此前只有浏览器默认样式
(灰三角 + 裸文字),与文章 Catppuccin 设计语言断档。

复用代码块/blockquote 的视觉语言:同款底色 + 边框 + 16px 圆角;
自绘 chevron 替代原生三角,展开旋转 90° 平滑过渡;
补 hover 浅背景与 :focus-visible 轮廓(WCAG 2.4.7);展开内容补
左右内边距与首尾 margin。

作用域 .md-content details:not(.toc),与 TOC 专用样式互不干扰。
2026-07-16 16:31:00 +08:00
xfy
45122fc687 fix(post): scrollToHash 增加一次性守卫,切主题不再跳回 URL hash
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
PostContent 的 use_effect 读取 resolved_theme() 建立订阅,主题切换时重跑
(为重跑 mermaid 主题)。但该 effect 同时调用 __scrollToHash,主题切换会
把页面跳回 URL hash 位置——即使该 hash 早已不在用户视野。

scrollToHash 是首屏异步取数后的补救滚动(弥补骨架屏阶段标题 DOM 缺失导致的
原生 fragment-scroll 失效),本质上是一次性的,不应随主题切换重跑。增加
did_scroll 信号守卫,仅首次 effect 运行时调用。
2026-07-16 16:22:42 +08:00
xfy
f6b7ab4333 fix(post): scrollToHash 增加 ResizeObserver 布局稳定期,修正 mermaid 异步渲染导致的锚点落点偏移
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
直接访问 /post/slug#heading 或刷新该 URL 时,目标标题停在屏幕中间偏下,但点击目录
或标题跳转却正常。根因是时序竞争:

post_content 的 use_effect 同步依次调用 __initMermaid(注册 IntersectionObserver,
异步)→ __scrollToHash。scrollToHash 的"立即 + 两帧"校正只覆盖浏览器原生
fragment-scroll;而 mermaid 懒加载(加载 ~1MB bundle → render → pre.innerHTML=svg)
是数百毫秒的异步链,发生在两帧之后。mermaid 块从源码文本膨胀成 SVG 图时撑高内容,
把目标标题推离已校准的落点。mermaid.ts 渲染完成后无任何落点修正。点击目录正常是
因为那时 mermaid 已渲染、布局稳定。

解法:scrollToHash 末尾新增 stabilizeScrollOnResize——在 STABILIZE_WINDOW_MS(2s) 内
用 ResizeObserver 观察 .post-content 容器,检测到布局变化即用 rAF 合并重新校正一次
落点。与 mermaid 解耦,同时覆盖图片/字体等所有异步位移源。用户主动滚动(wheel/
touchmove/keydown)或超时后自动停止,不与交互打架。重入安全:主题切换重跑 use_effect
时先 dispose 上一次稳定器,避免泄漏/叠加。

测试(hash-scroll.test.ts)新增 3 例:resize 后再校正、用户 wheel 后停止、重入清理旧 RO。
2026-07-16 16:05:11 +08:00
xfy
1ea11b6434 style(checkbox): 文章页 task-list checkbox 改用 appearance:none 自绘圆角方框
原生 checkbox 形状随浏览器而异(Safari 圆角小框/Chrome 直角),
accent-color 只能上色无法统一形状。改为全自定义:
- appearance:none + 4px 圆角 + 1.15rem 方框
- 勾选时 accent 绿填充,对勾用内联 SVG background
  (<input> 是替换元素,吃不到伪元素)
- 亮色:中绿底 #40a02b + 白对勾(对比 ≥4.5:1)
- 暗色:浅绿底 #a6e3a1 + 深对勾 #1e1e2e(白对勾在浅绿上对比不足)
- 文章内 checkbox 是 disabled 展示态,故不加 focus/cursor 交互
2026-07-16 15:58:54 +08:00
xfy
e526da0571 style(code-block): 代码块字号从 13.6px 调整为 16px
.md-content pre code 的 font-size 由 0.85em 改为 1rem,
锚定根字号 16px 以提升代码块可读性。行间 code 保持 0.85em 不变。
2026-07-16 15:52:58 +08:00
xfy
445b92f407 fix(mermaid): 改用 script 标签加载 IIFE bundle,修正全局变量取值
根因:mermaid bundle 是 IIFE 格式(与项目其他前端库一致),挂全局变量
而非 ES module export。但 mermaid.ts 错用了 import() 取 .default——
IIFE 无 export,mod.default 是 undefined,导致 mermaid.initialize 调用
TypeError,流程图永远不渲染(数学公式不受影响,因为是服务端渲染)。

修复:
- mermaid.ts: loadMermaidBundle 改用动态注入 <script src=/mermaid/mermaid.js>
  标签,加载后从 window.MermaidRenderer 取(与 codemirror/lightbox 的
  Reflect::get(window,...) 模式一致)
- mermaid-renderer/index.ts: 显式 globalThis.MermaidRenderer = mermaid,
  避免 vite IIFE 把 export default 编译成 .default 多一层
- markdown.rs: mermaid 代码块跳过 syntect 高亮(无语法定义且会包 <span>
  污染 textContent),直接输出转义纯源码

前端 28 测试 + Rust 557 测试 + 双 target 编译通过。
2026-07-16 15:36:07 +08:00
xfy
ac04b23b59 chore(build): make clean 清除 static/ SSR 缓存目录
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-16 15:15:55 +08:00
xfy
f613460742 fix(docker): 修复 DX 下载 case 分支行延续导致的解析失败
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
Dockerfile 的 RUN 指令只在行尾有反斜杠时延续到下一行。
DX 下载步骤的 amd64/arm64 分支把 DX_TRIPLET 和 DX_SHA256 拆成两行,
首行缺少续行符,Docker 把次行 DX_SHA256=... 当成新的顶层指令,
报错 unknown instruction: DX_SHA256=...。

改成与同文件其它 case 块(Node/Tailwind/musl)一致的写法:
每个分支单行、以 ;; 收尾。SHA256 值已对照 GitHub 官方 .sha256 旁挂文件核对一致。
2026-07-16 15:15:12 +08:00
xfy
438a5e1046 fix(ssr): 文章写入后物理删除 SSR 磁盘缓存,根治'重建后内容不更新'
Dioxus 0.7 增量渲染器把每路由 SSR 结果落盘到 static/<route>/index/<hash>.html,
只暴露 invalidate_after(ttl) 一个失效手段,无按路由失效的公开 API。
之前 bump_global_generation 在 Dioxus 0.7 不生效(ssr_cache.rs 注释已承认),
导致文章重建/编辑后,即便 DB content_html 已是新内容,SSR 缓存仍返回旧页面,
最长滞后 SSR_CACHE_SECS(默认 3600s)。本次 katex/mermaid 渲染不生效即因此。

修复:在 ssr_cache.rs 新增物理删除缓存的函数:
- invalidate_ssr_route(route): 删 static/<route>/ (含路径穿越防护)
- invalidate_ssr_all_public(): 删 static/ 下除 .well-known/admin 外全部
- invalidate_ssr_home(): 删 static/index/ (细粒度,allow dead_code)

所有写路径(create/update/rebuild/delete/trash)在事务提交后调用:
- 单篇操作失效 /post/{slug} + 全量公开页(列表项变化影响首页/归档/标签)
- 批量操作直接全量失效

世代号保留作 X-SSR-Generation 可观测性,实际失效靠删文件。
顺带修 katex.rs 的 clippy(field_reassign_with_default)+ cmp_owned。

556 Rust 测试 + clippy -D warnings + 双 target 编译通过。
2026-07-16 15:11:34 +08:00
xfy
09ca480842 chore(docker): 移除 gh-proxy,改用 GitHub 直连下载 dx 与 Tailwind
Dioxus CLI 与 Tailwind CSS 独立二进制此前经 gh-proxy.com 镜像下载,
现移除 GH_PROXY 前缀及相关注释,直接从 GitHub Releases 拉取。
2026-07-16 14:51:21 +08:00
xfy
78a8d188d1 fix(admin): 重建结果消息改绝对定位,避免撑高容器顶起按钮
RebuildCacheBar 原用 flex-col 把「已重建 x 篇文章」消息排在按钮下方,
参与文档流。其祖先 header 为 md:items-end(底边固定),消息出现时
新增高度全部转化为按钮上移,「重建内容/重建全部」被顶起、「发布文章」
相对下沉,三者错位。

改为外层 relative + 消息 absolute top-full 脱离文档流:出现/消失都
不影响按钮行高度,布局稳定。双 target 编译通过。
2026-07-16 14:36:33 +08:00
xfy
f39cb35a31 docs: 补充数学公式与流程图架构说明
AGENTS.md 新增 'Math & Diagrams' 章节详述两条渲染路径:
- KaTeX 服务端渲染:katex-rs + pulldown-cmark ENABLE_MATH + sanitizer span
  白名单 + 自托管 CSS/字体(make katex-css)
- mermaid 客户端懒加载:IntersectionObserver + 动态 import 独立 bundle

同步更新:Server-only deps 加 katex-rs、Frontend Lib 表加 mermaid-renderer 行、
Build Artifacts 加 public/{katex,mermaid}、markdown.rs 描述。
2026-07-16 14:24:38 +08:00
xfy
70edc5e46c feat(frontend): mermaid 流程图懒加载渲染
mermaid 无官方 SSR 支持,采用客户端 IntersectionObserver 懒加载:服务端
markdown.rs 只产出普通 <pre><code class="language-mermaid"> 代码块,
前端在块进视口时动态 import 独立 bundle(~1MB)渲染成 SVG。

新增 libs/mermaid-renderer/:mermaid 11.16 IIFE bundle,输出 public/mermaid/
yggdrasil-core/mermaid.ts:扫描 language-mermaid 块,视口可见时动态 import
  /mermaid/mermaid.js(单例缓存),mermaid.initialize 适配 light/dark 主题,
  securityLevel=strict;渲染失败加 .mermaid-error class 保留源码;幂等守卫
post-content.ts:initCopyButtons 跳过 language-mermaid 块(图无需 copy)
post_content.rs:use_effect 调 __initMermaid('.post-content', theme),
  读 use_resolved_theme() 传主题并建立订阅

新增 6 个 mermaid 单测(扫描/主题/幂等/非mermaid/未命中/错误回退)。
550 Rust + 28 前端测试全过,双 target 编译通过。
2026-07-16 14:22:43 +08:00
xfy
218533402b feat(css): 自托管 KaTeX CSS 与 woff2 字体
服务端 katex-rs 只输出 HTML span,字体/排版靠配套 CSS。从 npm 包 katex
0.16.x 的 dist/ 拷贝 katex.min.css + woff2 字体(省去 woff/ttf)到 public/katex/,
CSS 用相对 url(fonts/) 引用故两者须同级。

- libs/package.json: katex ^0.16.22 作 workspace 根 devDependency
- Makefile: 新增 katex-css target,接入 build/build-linux/dev;clean 清 public/katex
- Dioxus.toml: [web.resource]/[web.resource.dev] style 列表注册 /katex/katex.min.css
- .gitignore: public/katex 作构建产物忽略(同 highlight.css 等)

make katex-css 验证通过:23KB CSS + 20 个 woff2 字体就位。
2026-07-16 14:15:20 +08:00
xfy
84f6223c68 feat(markdown): 评论支持数学公式渲染
- comments/markdown.rs: 开启 ENABLE_MATH,InlineMath/DisplayMath 事件
  渲染成 katex HTML 注入事件流(评论不用块级 <p> 包裹,保持紧凑)
- sanitizer.rs: 评论路径 span 白名单加 style(KaTeX 内联定位 style 需保留,
  与文章正文 sanitizer.rs:382 对齐)
- 新增 3 个评论公式测试(内联/块级/style 保留)

双 target 编译通过,24 个评论测试 + 36 个 sanitizer 测试全过。
2026-07-16 14:12:20 +08:00
xfy
95fe9d862d feat(markdown): 文章支持数学公式 SSR 渲染
render_markdown_enhanced 事件循环新增 InlineMath/DisplayMath 分支:
- $...$ 内联公式调 katex::render_inline 注入 span
- $$...$$ 块级公式用 <p class="math-display"> 包裹 katex 输出
- 标题内公式按内联渲染,不产生块级 <p>

pulldown-cmark 的 Options::all() 已含 ENABLE_MATH,无需改 options。
新增 5 个端到端测试(内联/块级/标题内/坏公式不破坏全文)。
双 target 编译通过,16 个 markdown 测试全过。
2026-07-16 14:11:05 +08:00
xfy
891a576dc0 feat(deps): 引入 katex-rs 服务端数学公式渲染
纯 Rust 的 katex-rs 0.2.4 重实现 KaTeX,把 TeX 公式渲染成 HTML span,
供后续 pulldown-cmark 的 InlineMath/DisplayMath 事件调用。

- Cargo.toml: katex-rs 作为 optional dep 经 server feature 启用
- src/api/katex.rs: thread_local 缓存 KatexContext/Settings(因含 RefCell
  宏表非 Sync,不能放全局 static);OutputFormat::Html 避开 MathML 标签
  使 sanitizer 无需开白名单;throw_on_error=false 防坏公式中断全文
- src/api/mod.rs: #[cfg(feature="server")] 注册 katex 模块

双 target 编译通过(server + wasm32),4 个单测通过。
2026-07-16 14:09:14 +08:00
xfy
1935321da1 build(deps): 升级 TypeScript 至 7.0.2(Go 原生编译器)
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
TypeScript 7 已将 Go 重写版(代号 Corsa)合并进主线 typescript 包,
npm latest 即 7.0.2,无需再依赖 @typescript/native-preview 预览包。

本项目单点改动:libs/package.json devDependencies.typescript ^6.0.3 -> ^7.0.2
(6 个子包均从 workspace 根继承版本,无需逐包改动)。

兼容性核对(TS 7 破坏性变更 vs tsconfig.base.json):
- target ES2020 / module ESNext / moduleResolution bundler 均在支持范围
- 未使用 baseUrl / node10 / es5 等 TS 7 移除的选项
- esModuleInterop=true / strict=true 满足 TS 7 的新下限要求
- css 副作用导入已有 css.d.ts 声明,满足 noUncheckedSideEffectImports 新默认值

验证:typecheck(6 包 tsc --noEmit) + build(6 包 tsc && vite build)
+ test(187 个 vitest 用例)全部通过。
2026-07-16 13:36:54 +08:00
xfy
debe7dabe3 fix(test): Docker 集成测试在无 daemon 环境优雅跳过而非失败
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
docker.rs 的 4 个 test_run_in_container_* 此前直接 unwrap Docker 客户端,
在没有 Docker daemon 的环境(如 CI、纯服务端交叉编译检查)会 panic 失败,
拖累整套 cargo test 的退出码——即便这些测试本就无法在那里运行。

加 require_docker() 守卫:socket 缺失(DOCKER_CLIENT == None)或 daemon
无响应(轻量 version() 探测)时返回 None,测试 eprintln + 提前 return。

选动态探测而非 #[ignore]:#[ignore] 会在所有环境一律跳过、需
cargo test --ignored 显式触发;探测守卫让测试在有 Docker 时自动运行,
在无 Docker 的 CI 上静默放行,且在 --nocapture 下可见跳过原因。

验证:cargo test --bin yggdrasil 从 535 passed / 4 failed → 539 passed / 0 failed。
2026-07-16 13:22:49 +08:00