1090 Commits

Author SHA1 Message Date
xfy
690e00e194 refactor(unwrap): 消除非测试代码中的裸 .unwrap(),改为带不变量说明的 .expect()
panic="abort" 下任何 panic 直接杀进程,与 unwrap 的「稍后处理」语义冲突。
本轮把所有可证不变量的裸 unwrap 改写为带说明的 expect,并修复 time.rs 中
u32→i32 转换在大 ms 值下的潜在溢出 panic(clamp 到 i32::MAX)。

- src/utils/time.rs: sleep_ms 的 ms.try_into().unwrap() 改为 clamp 到
  i32::MAX,避免超大延时值溢出;window/set_timeout 的 expect 补充上下文说明
- src/utils/text.rs: 6 个静态正则的 unwrap 改为 expect,统一说明「编译期校验」
- src/api/{auth,comments/helpers}.rs: EMAIL_REGEX 同上
- src/api/markdown.rs: IMG_RE / TABLE_RE 同上
- src/api/rate_limit.rs: NonZeroU32::new(val.max(1)) 与 Quota::with_period
  补充不变量说明
- src/api/image.rs: HeaderValue::from_str(&etag).unwrap() 改为 expect 说明
  etag 仅含 ASCII hex;复用 etag_value 避免 clone
- src/middleware.rs: 静态 302 重定向响应的 unwrap 改为 expect
- src/components/post/post_content.rs、src/theme.rs: WASM 上下文 window().unwrap()
  改为 expect 说明仅浏览器执行

保留的 unwrap/expect(合理不变量,已在注释中说明):
- DB_POOL LazyLock 闭包(已有详细注释 + validate_database_url 前置校验)
- csrf.rs 静态 forbidden 响应
- highlight.rs syntect 内置 Plain Text 语法
- bin/generate_highlight_css 构建工具
- comments/* 的 #[cfg(not(server))] unreachable!() WASM stub
2026-07-23 18:26:19 +08:00
xfy
d1013dfa08 refactor(mhchem): re! 宏编译失败降级为不匹配,补上 panic=abort 缺失的防御
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
re! 宏原先 LazyLock::new(|| Regex::new($pat).unwrap()),在 panic="abort" 下
任何正则转录错误(如曾经的 ". __* " InvalidClass)都会直接杀进程,且 to_tex
的 catch_unwind 在 abort 模式下无法兜住。

引入 compile_or_log(pat) -> Option<Regex>:编译失败记 tracing::error 日志
并返回 None(绝不 panic)。封装为 CompiledPat(Option<Regex>) newtype,把
「坏正则降级为不匹配」的语义集中在一处:

  - captures_head(input) -> Option<Captures>:坏正则/未匹配统一 None
  - is_match(input) -> bool:坏正则/未匹配统一 false

re! 宏改为产出 &CompiledPat,调用方直接 re!("...").captures_head(...) /
.is_match(...),无需再到处写 re_ref(...)? 或手写 None 回退。状态机遇到
坏模式自然退到 "else" 兜底分支产出原样字符,不影响整体渲染。

适配点:Pat::Re、pat_match_head、match_pattern 的 re_match 闭包、
pat_enumber/state_of_aggregation/amount/formula、1/2 动作、mhchem_tables.rs
的 ce_action、3 个 static LETTERS/GREEK/ONE_GREEK_RE。

回归测试 compile_or_log_degrades_bad_regex_without_panic 断言坏正则返回 None
不 panic。clippy 干净,622 测试全过。
2026-07-23 18:16:22 +08:00
xfy
a03fbac88c fix(mhchem): 修复 find_observe_end 逐字节扫描多字节字符的 panic
find_observe_end 用 `let bytes = input.as_bytes(); let mut i = start; while i < len { ...; i += 1; }`
逐字节步进,对每个位置做 `&input[i..]` 切片与 `bytes[i] as char` 花括号判定。
化学公式若含多字节 UTF-8 字符(如中文「浓」占 3 字节),i 会落在字符内部:

  - `&input[i..]` 触发 `byte index N is not a char boundary; it is inside '浓'` panic;
  - 在 panic=abort 下直接 SIGABRT 杀进程;
  - `bytes[i] as char` 把多字节字符首字节误判为花括号,还静默损坏输出。

触发路径:rebuild_content_html 重建含中文化学式(如 \ce{浓H2SO4})的文章时崩溃。

修复:改用 `input.char_indices().skip_while(...)` 按字符边界步进,i 始终落在
char 边界上,`a` 直接是解出的 char。start 校验对齐到边界后跳过。

新增:
- 回归测试 ce_multibyte_char_in_braces_does_not_panic:覆盖触发输入。
- 保护性测试 ce_arbitrary_multibyte_does_not_panic:穷举 emoji/日韩文/组合字符/
  四字节区,覆盖花括号/方括号/\frac/$...$ 全部走 find_observe_end 的路径。

注:多字节字符的状态机转译输出质量(如 ce("浓H2SO4") 的字符重复)是独立的
既有问题,mhchemParser 本为 ASCII 化学符号设计;本提交只修 panic,不扩大范围。
2026-07-23 18:00:41 +08:00
xfy
feaf76bcd9 fix(mhchem): 修复 ". __* " 正则转录错误导致的 panic=abort 崩溃
mhchem 转译器中 ". __* " 模式的 regex 被误抄成
  ^([.\u{22C5}\u{00B7}\u{2022}|[*])\s*
即把 alternation 的 `|` 放进了字符类内部。fancy-regex 把 `[...|[*]`
当作单个畸形字符类,报 ParseError(20, InvalidClass);`re!` 宏的
`.unwrap()` 因此 panic。

上游 mhchemParser 4.2.2 是 `[...]|[*]`(alternation 在类外),修正为
  ^([.\u{22C5}\u{00B7}\u{2022}]|[*])\s*

触发路径:用户提交含 `\ce{·}`/`\ce{•}`/`\ce{.}` 等化学式的文章 →
render_markdown_enhanced → katex::render_* → expand_chem → mhchem::ce,
首次编译该 LazyLock 正则即 panic。

关键放大因素:本项目 release profile 为 panic="abort",`to_tex` 内的
`std::panic::catch_unwind` 在此设置下无法捕获 panic,进程直接 SIGABRT。
修正了 `ce`/`pu` 的文档注释,不再谎称"绝不 panic"。

新增:
- 回归测试 ce_dot_bullet_bond_inputs_dont_panic:覆盖触发输入。
- 保护性测试 all_match_patterns_compile:强制编译 match_pattern 全部分支,
  任何同类转录错误在测试期而非生产期暴露。
2026-07-23 17:49:20 +08:00
xfy
3e9a3aa70d docs(mhchem): 修复模块级文档中的 broken intra-doc links
cargo doc 在 --document-private-items 下报 7 个 unresolved link 警告,
全部源自 src/api/mhchem.rs 的模块级 //! 文档:

- 公开 API ce/pu 用简写 [`ce`] 无法解析,改用全路径链接
  [`ce`](crate::api::mhchem::ce) / [`pu`](crate::api::mhchem::pu)。
- 私有项 Buffer/Parsed/Field/find_observe_groups/build_transitions 即便
  写全路径也无法被 intra-doc link 解析器识别(rustdoc 对私有项的限制),
  改回普通反引号代码,不再生成链接。这些是模块内部实现细节,本就无需
  跨项跳转。

修复后 make doc 零警告。
2026-07-23 17:48:06 +08:00
xfy
089fe88a75 fix(docker): 复制 patches/ 目录以修复 pnpm install ENOENT
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
pnpm-workspace.yaml 声明了指向 patches/@tiptap__markdown@3.27.3.patch 的
patched 依赖,但 Docker 构建在 COPY package manifest 阶段未包含
patches/ 目录,导致 pnpm install --frozen-lockfile 因找不到 patch
文件而失败。将 libs/patches/ 加入 COPY 即可。
2026-07-23 16:45:00 +08:00
xfy
970ff8dc5b chore: release v0.6.0 v0.6.0 2026-07-23 16:33:07 +08:00
xfy
f2216119ae chore(markdown): 格式化测试中的长字符串字面量
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-23 16:30:31 +08:00
xfy
07d737af0f fix(markdown): 修复窄表格在 table-wrap 内边框被裁切
overflow-x:auto 会强制 overflow-y 也建滚动盒,裁切 table 向外 spread 的 1px
box-shadow 描边,导致不需要滚动的窄表格右下角边框断裂。

给 .table-wrap 加 padding:1px 为描边预留空间,负 margin:-1px 抵消外部尺寸
偏移,使 wrap 不撑大父容器、左上仍贴齐正文边缘。
2026-07-23 16:18:12 +08:00
xfy
485d4b1cc4 fix(markdown): 修复移动端表格无法横向滚动
宽表格在移动端被外层 <main> 的 overflow-hidden 直接裁掉,既看不到也滚不到。
根因是三层叠加:裸 table 无滚动容器、外层裁剪、th 强制 nowrap 加剧超宽。

- markdown.rs: 新增 wrap_tables,给每个 table 套 <div class=table-wrap>
  滚动容器(仿 wrap_images_with_blur 模式,sanitizer 放行 div+class)
- input.css: .table-wrap 设 overflow-x:auto;table 加 min-width:100% 让宽表
  撑开触发滚动;768px 下对旧文章裸 table (.md-content > div > table) 兜底
  block 化,新文章因多一层 div 不受影响、圆角视觉完整
- frontend_layout.rs: main 的 overflow-hidden 改 overflow-x-clip,只裁 x
  且不建滚动容器,让内部 .table-wrap 滚动生效

旧文章经 CSS 兜底立即修复;新文章用渲染器包裹视觉完整。可在 /admin/posts
点 rebuild all 让旧文章也升级到 div 包裹。
2026-07-23 16:07:11 +08:00
xfy
77a22bbfb1 feat(editor): tiptap 代码块 mermaid 实时预览
mermaid 代码块( ```mermaid )在富文本编辑器里此前只是普通源码,作者看不到
渲染效果。现在像数学公式、脚注一样在编辑器内直接预览:源码下方加预览区,
debounce 500ms 后渲染成 SVG,跟随站点 light/dark 主题切换,与前台文章页
渲染视觉完全一致。

一致性保证(核心):
- 同一份 mermaid 运行时:编辑器与前台都加载 /mermaid/mermaid.js(11.16.0)
- 同一套主题变量:经 @yggdrasil/shared 单一真相源(上一提交下沉)
- 同一份渲染配置:theme:'base' + securityLevel:'strict' + flowchart.curve:'basis'
- 同一主题切换机制:监听 THEME_CHANGE_EVENT(与 codemirror-editor 同范式)

实现:
- mermaid.ts:加载 bundle + renderMermaid(source, theme) 封装,与前台同逻辑
- code-block-view.ts:mermaid 块构造时建预览区;源码变化 debounce 重渲染;
  renderToken 防竞态(快速改动只保留最新);主题事件重渲染;language 切入/切出
  mermaid 时创建/移除预览区;destroy 清理 timer+监听+token
- 渲染失败显示错误态(accent 色左边框),源码仍可编辑修正
- 样式对齐前台 pre[data-mermaid-rendered]:flex 居中、SVG 等比缩放、Latte/Mocha 双主题
- 测试 mock mermaid 运行时,覆盖预览区生命周期、debounce、竞态、主题、错误、清理

服务端 markdown.rs / Dioxus.toml / Rust-JS 桥零改动。
2026-07-23 15:13:07 +08:00
xfy
800129e245 refactor(shared): 下沉 mermaid Catppuccin 主题变量到 shared 包
前台 yggdrasil-core 的 LATTE_VARS/MOCHA_VARS 是 mermaid 配色唯一真相源。
后台 tiptap 编辑器要实现 mermaid 预览且与前台视觉一致,需复用同一套变量;
若复制一份会立即产生漂移风险,而「主题一致性」正是编辑器预览的核心目标。

按 shared 包「单一真相源」的设计意图,把两套主题变量(~100 行)+ MermaidThemeVariables
类型 + mermaidThemeVarsFor(theme) 工具函数搬到 shared。yggdrasil-core 改 import
自 shared,删除本地定义——行为零变化(同一份变量,只换引用源)。

前台 37 个测试全绿回归,含 10 个 mermaid 测试验证亮/暗主题传入正确调色板。
2026-07-23 15:12:48 +08:00
xfy
86781d8bbc feat(editor): tiptap 富文本编辑器内脚注所见即所得
脚注 [^id] 引用与 [^id]: 内容 定义此前在富文本模式下只是裸文本,
作者无法看到渲染效果。现在像数学公式一样建模为 atom 节点,在编辑器内
直接预览:引用渲染成上标编号、定义渲染成可双击编辑的卡片块,编号按
引用首次出现顺序分配,与线上文章页 render_markdown_enhanced 的 GFM
模式一致。

- footnoteRef(inline): [^label] 解析成上标节点,双击浮层预览定义内容
- footnoteDef(block): [^label]: 内容 解析成卡片块,双击进入 textarea 编辑
- FootnoteNumbering: ProseMirror plugin,文档变化后重算编号表写 storage,
  并直接遍历 DOM 刷编号文本(不 dispatch transaction,避免循环);编号是
  派生视图,不进文档模型
- 多行定义序列化用 4 空格缩进续行,与 pulldown-cmark GFM 续行规则对齐
- 样式复用文章页 .fn-ref/.footnote-definition 视觉语义,限定 .tiptap-editor
  作用域,Latte/Mocha 双主题

脚注改走 atom 节点的 renderMarkdown 后,不再经过 escapeMarkdownSyntax 的
文本转义路径,unescapeFootnoteSyntax 后处理及其三处调用随之移除(转义问题
从根上消失)。测试从旧的"转义+还原"模型重写为验证节点解析、序列化往返、
编号实时分配。服务端 markdown.rs / input.css 零改动。
2026-07-23 14:33:25 +08:00
xfy
f569258633 fix(editor): 隐藏无语言标识代码块的空工具栏顶栏 2026-07-23 14:00:42 +08:00
xfy
67406f661a refactor(middleware): 抽出 ssr_generation/version_headers 中间件到 middleware.rs
main.rs 的 serve() 闭包内有两个内联 async fn 中间件
(ssr_generation_middleware、version_headers_middleware),与已抽出到
middleware.rs 的 add_cache_control/admin_guard 同类却散落在入口,无法独立测试。

将其移入 middleware.rs 作为 pub(crate) 函数,serve() 闭包改用全路径
crate::middleware::xxx 引用(与现有中间件一致),行为不变。

签名归一:原内联用 axum::http::Request<Body>,迁移后改用
axum::extract::Request(与 add_cache_control/admin_guard 一致;二者是同一
类型的别名,from_fn 接受不变)。

挂载点说明(版本头置于最外层合并 router)留在 main.rs 调用处注释,描述的是
挂载决策而非函数本身。
2026-07-23 13:56:51 +08:00
xfy
b25abcb9d8 chore(deps): 更新 tokio 与 regex 依赖包版本
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-23 13:38:10 +08:00
xfy
644db48d17 fix(lint): 修复 Biome 检查错误
- mermaid-renderer/src/index.ts: import 与后续语句间补空行(organizeImports)
- tiptap-editor footnote.test.ts: 局部 unescape 重命名为 unescapeFootnote,
  避免遮蔽全局 restricted name(noShadowRestrictedNames)
2026-07-23 13:36:11 +08:00
xfy
db74a96de1 style: 格式化 Rust 与 TS 代码 2026-07-23 13:34:27 +08:00
xfy
17d596c512 refactor: 清理死代码与冗余,修复 mhchem 模块的 clippy 阻断
mhchem 机械移植引入了一个 deny-by-default 的 useless_attribute 硬错误
(mhchem_tables.rs 顶部的 outer #[allow] 挂在 use 语句上),导致
make lint / CI check job 失败。顺带清理同模块的死代码与风格项,以及仓库
内其他确认无引用的死代码。

- mhchem_tables.rs: 删除无效 outer allow、未使用的 `use super::*`、
  pq_action 未用形参 buf→_buf;v.get(0)→v.first()
- mhchem.rs: 删除从未调用的 Field::empty;修正 &*RE→&RE、Default 字段
  重赋值;Parsed/Out 加 large_enum_variant allow(镜像上游结构,
  经 Vec 进堆,非栈热路径)
- codemirror_bridge.rs: 删除 SqlSchema/SqlTable 上过期的 dead_code
  allow——get_db_schema 早已消费这两个类型
- ssr_cache.rs: 删除生产路径从未调用的 invalidate_ssr_home(首页已由
  invalidate_ssr_all_public 覆盖),修正相关注释与测试
- auth.rs: 删除预留给未实现用户管理功能的 invalidate_user_sessions

验证:cargo clippy --all-targets --all-features -- -D warnings 通过;
mhchem/ssr_cache 共 20 项测试通过。
2026-07-23 13:32:46 +08:00
xfy
6276bab6c6 fix(sanitizer): 允许 KaTeX 渲染所需的 svg 与 path 标签及绘图属性
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
KaTeX 在 SSR 渲染根号(\sqrt)、矩阵竖线/括号(\begin{pmatrix})、大括号、上/下括号、箭头等复杂公式时,通过 <svg> 和 <path> 绘制线条与图标。

原先 HTML 消毒器 (sanitizer) 的标签白名单中未包含 svg / path,导致 lol_html 过滤正文与评论 HTML 时将 <svg> 和 <path> 剥离,致使根号顶线与矩阵括号丢失。

改动:
1. DEFAULT_ALLOWED_TAGS 增加 svg / path 标签放行
2. Sanitizer 属性白名单放行 svg (xmlns, width, height, viewbox, preserveaspectratio, style) 与 path (d) 绘图属性
3. 补充 sanitizer 与 markdown 单元测试防御回归
2026-07-23 13:27:13 +08:00
xfy
bcd13958ac feat(katex): 移植 mhchem 化学公式转译器(\ce/\pu → LaTeX)
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
katex-rs 默认无 mhchem 解析器,\ce/\pu 化学公式渲染为红字(实测正确页面 648
的 137 个公式中 32 处化学公式坏掉,含 \ce×24 + \pu×8)。

从 mhchemParser 4.2.2(Apache-2.0)机械移植状态机转译器为纯 Rust:
- src/api/mhchem.rs:数据模型 + 模式匹配 + go() 主循环 + texify 输出 + 公开 API
- src/api/mhchem_tables.rs:14 个状态机转移表 + 机器局部动作(ce/pu/...)
- 模式含 lookahead (?=)/(?!),Rust regex crate 不支持,故用 fancy-regex
  (已被 syntect 间接引入,此处显式声明为直接 server 依赖)
- ce/pu 包裹 catch_unwind,任何内部异常回退原样输出,绝不 panic
- katex.rs 新增 expand_chem:渲染前嵌套花括号配对扫描 \ce/\pu 预转译
- 行尾 ^ 气体符号转译为 \uparrow,消解原 mhchem 解析错误
- 8 个 mhchem 单测 + 5 个 katex 集成测试(水/反应箭头/气体箭头/单位/络离子)
2026-07-23 12:23:53 +08:00
xfy
c6999763c0 fix(editor): 修复 tiptap-markdown 过度转义导致显示损坏
@tiptap/markdown 的 escapeMarkdownSyntax 对每个非代码文本节点的 */_/>
无差别转义,导致 content_html→content\_html、>→&gt;、字面星号被反复
转义累积,前台表格/加粗/引用块显示为裸 markdown 文本。转义幂等且自增强
(已转义的 \* 被 marked 解析为 escape token→再转义→粘住),只有源码模式
保存原文才能清除。

pnpm patch 持久化修补上游包(提交进 libs/patches/,pnpm install 自动应用):
- escapeMarkdownSyntax 改为 CommonMark 风向感知:只在真正构成强调开/闭
  边界的位置转义 */_(一侧 space/punct、另一侧 other),词内位置不转义
- encodeHtmlEntities 降级为 encodeLimitedHtmlEntities:只编码 < 和 &,
  移除 > (文本节点内的 > 不构成 blockquote,编码只会产生 &gt; 损坏)
- 同步修补 src/dist ESM/dist CJS 三处
- 8 个往返单测覆盖词内下划线/行内>/字面星号/真实强调/多轮稳定
2026-07-23 12:04:25 +08:00
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