1056 Commits

Author SHA1 Message Date
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
xfy
fa8b1d9968 test: 补齐安全关键路径的单元测试盲区
针对前期测试评估发现的高性价比纯函数与安全不变量盲区补测,
共新增 35 个回归测试(全部通过),分布如下:

- infra/docker.rs (+5): 扩展 build_host_config 的隔离断言——cap_drop=ALL、
  security_opt=no-new-privileges、pids_limit=64、memory_swap==memory(禁 swap)、
  tmpfs 的 exec 仅落在 /tmp、ulimits 只保留 nofile(无 nproc)。这些配置若被
  误删会让容器隔离悄悄失效,此前 test_host_config_generation 仅断言 cpu/memory。

- api/code_runner/execute.rs (+8): validate_exec_request 此前零测试。覆盖
  语言白名单(大小写/空白容忍、多 token 拒绝、未注册拒绝)、源码大小边界
  (恰好 max 放行 / max+1 拒绝)、校验顺序(语言优先于大小)。

- api/database/export.rs (+12): is_simple_ident / parse_source 此前零测试。
  覆盖表名白名单(SQL 注入 / 路径穿越 / Unicode 拒绝)、query: 来源的只读
  AST 校验(INSERT/UPDATE/DELETE/DROP/TRUNCATE/CREATE/ALTER/GRANT 全拦截)。

- api/sanitizer.rs (+10): 补 XSS 攻击向量回归——事件处理器属性(onerror/onload/
  onclick + 大小写混淆)、script/style 连内容移除、iframe/object/svg/form 等
  危险标签整体移除、javascript:/vbscript: scheme(含 HTML 实体编码绕过)、
  data URI 在文章正文路径(allow_data_uri=false)的拒绝、svg data URI 风险点锁定。

顺带记录一个既有可维护性问题:docker.rs 的 4 个 test_run_in_container_*
依赖真实 Docker daemon,无 daemon 环境会失败而非跳过,建议后续加 #[ignore]
或 daemon 探测守卫。本次未改动它们。
2026-07-16 11:55:58 +08:00
xfy
fdf2243299 docs(env): 补全 .env.example 缺失的 5 个环境变量
代码中经 env::var 读取但示例文件遗漏:

- EXPOSE_VERSION_HEADERS (main.rs:232, 安全相关, 控制版本暴露响应头)
- SYSINFO_SAMPLE_SECS (sysinfo_sampler.rs:46, 后台采样间隔)
- CODE_RUNNER_MAX_SOURCE_BYTES (runner_config.rs:67)
- CODE_RUNNER_QUEUE_TIMEOUT_SECS (runner_config.rs:79)
- CODE_RUNNER_TASK_TTL_SECS (runner_config.rs:83)

默认值与代码一致, 注释风格沿用现有分组(安全段 / tuning 段 /
Code Runner 段)。交叉核对确认示例文件无多余项。
2026-07-16 11:42:30 +08:00
xfy
d1c41e8fc5 fix(wasm): hooks 模块移出 server gate,双端可见
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
mod hooks 原被 cfg(feature = "server") 包裹,WASM 前端无法引用 hooks 中的
代码。移出后 server 与 WASM 两端均能访问 hooks 模块。

同步修正 backup.rs 的 import 排序(cargo fmt 自动调整)
2026-07-16 11:37:13 +08:00
xfy
5b6cb7d796 feat(middleware): COMPRESSION_ALGORITHMS 默认值改为 off
未设置环境变量时不再默认启用全部压缩算法,改为默认关闭。显式设为
"all" 或具体算法组合仍可按需开启。

- src/middleware.rs: fallback 由 "all" 改为 "off",同步更新文档注释
  与默认值测试(compression_default_env_is_all → _is_off)
- .env.example: 示例值与说明同步更新
2026-07-16 11:33:14 +08:00
xfy
593c0322bc fix(server): 端口被占用时优雅退出而非 panic
dioxus::server::serve() 内部对 TcpListener::bind 失败直接
unwrap(dioxus-server 0.7.9 launch.rs:143),端口占用会触发
裸 panic + SIGABRT。在迁移完成后、serve() 前先探测同一地址,
失败走统一的 exit(1) 路径并给出可操作的提示(lsof / PORT)。
2026-07-16 11:23:45 +08:00
xfy
c2263e6bf1 chore(lint): 修复 rust-1.97 clippy useless_borrows_in_formatting 告警
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
新版 clippy 在 format! 参数的冗余 & 上新增 lint(与 mimalloc 改动无关),
移除 export.rs 的 &table_name 以保持 make lint 零告警。
2026-07-16 10:53:59 +08:00
xfy
5b183007a7 perf(server): 用 mimalloc 替换系统全局分配器
多线程高频小对象分配场景下,mimalloc 吞吐显著优于系统 malloc,且对全静态
musl 链接友好(生产 Docker 镜像即 musl 目标)。选 mimalloc 而非 jemalloc:
本项目生产 server 是全静态 musl 二进制,jemalloc 在 musl 上有 pthread_getname_np
(musl<1.2.3)/unprefixed-malloc 符号冲突的已知坑。

cfg 双门控(与项目双目标编译约定一致):
- feature=server:分配器只服务端二进制需要
- not(wasm32):mimalloc_rust 在 wasm32 上无法编译(Issue #76),前端走默认分配器

mimalloc 声明为 optional + 经 server feature 启用,与 argon2/uuid 等 server-only
依赖同栏位。验证:server native + WASM 双目标编译通过 + clippy 零告警。
musl 目标受本机无 musl-gcc 限制,需 Docker 内构建验证(同 zenwebp 等任何 C 依赖)。
2026-07-16 10:53:51 +08:00
xfy
8c907e4c67 chore(skill): 移除未使用的 writing-great-skills 技能及其 lock 注册
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-16 10:29:06 +08:00
xfy
dc24de0dbb chore: 更新 skills 配置与术语;新增 rust-advanced-performance 技能;清理 Makefile
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-16 10:27:51 +08:00
xfy
c6cb401522 refactor(admin): 拆分 system.rs 为 system/ 目录(按 tab 分文件)
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
pages/admin/system.rs 单文件 1792 行装 5 个 tab(数据库状态/服务器状态/SQL控制台/导出/备份恢复),按已有 api/database/ 的对称模式拆成 system/ 目录。

关键前提(已验证):5 个 tab 状态完全独立、互不共享,切换时父组件用 key 强制卸载,零跨文件状态耦合。

拆分结构(与 api/database/mod.rs 全路径风格对称):
- system/mod.rs: SystemTab enum + System 入口 + format_bytes(pub(super),3 tab 共用) + tests
- system/db_status.rs: DbStatusTab
- system/server_status.rs: ServerStatusTab + format_uptime(仅它用)
- system/sql_console.rs: SqlConsoleTab(codemirror_bridge 依赖)
- system/export.rs: ExportTab + urlencode(wasm32)
- system/backup.rs: BackupTab + BackupRow + urlencode_dl(自包含,不再跨文件依赖 export.rs)

路由侧零改动: admin/mod.rs 的 pub mod system + pub use system::System 不变(Rust 自动认 system/mod.rs)。双目标编译 + clippy 严格 + dx check + SystemTab 测试全部通过。
2026-07-15 18:28:39 +08:00
xfy
110be35c54 refactor: 抽 main.rs 中间件到 src/middleware.rs
main.rs 混合了配置校验、压缩层、cache-control、admin guard、迁移 runtime、后台任务、路由注册,其中 5 个 server-only 中间件/纯函数可独立测试。迁出到新建 src/middleware.rs(#![cfg(feature=server)]):CompressionAlgorithms + parse_compression_algorithms + compression_layer_from_env + cache_control_for_path + add_cache_control + admin_guard,连同 12 个单元测试。main.rs 调用点改为全路径 crate::middleware::xxx。无新 crate、无循环依赖。main.rs 756 -> 382 行,中间件现已可独立测试。
2026-07-15 18:20:56 +08:00
xfy
5a88531543 refactor(api): 收敛 mod.rs 逐行 allow 为文件级 #![allow]
api/posts/mod.rs(7处) 与 api/comments/mod.rs(3处) 在 pub use 行上方逐行加 #[allow(unused_imports)],而文件顶部已有 #![allow(clippy::unused_unit, deprecated)]。这些 allow 经核实真实必要(消费侧 server fn 多仅在 WASM 路径,SSR 编译时 unused),故收敛为 #![allow(..., unused_imports)] 等价替代,删掉 10 处逐行 attribute。clippy 严格模式 -D warnings 通过。
2026-07-15 18:16:49 +08:00