84b3da9db6
feat(editor): 新增 buildRunnableInfo 构造 runnable fence info string
...
纯函数,把语言+overrides 配置转成 'python runnable {...}' 形态。
dirty=false 省略 JSON,dirty=true 写 timeout/memory/network 三项,
字段顺序固定。
2026-07-06 13:45:11 +08:00
89bab26b90
fix(lint): themes.ts import 排序(type import 前置)
...
Biome organizeImports 要求 import type 排在普通 import 之前,
交换 @codemirror/view 与 @codemirror/state 顺序。make lint 全绿。
2026-07-06 11:45:14 +08:00
d3c2e41fd8
fix(codemirror): 编辑器塌缩导致上下背景割裂(height:100% 失效)
...
上一提交(3c10f72)用 `& { height: 100% }` 让 .cm-editor 撑满容器,
但实测无效——headless chromium 抓取 computed style 显示:
容器#ed: h=280px (min-height)
.cm-editor: h=63px ← 塌缩!只有内容高度
.cm-gutters: h=63px
.cm-content: h=63px
根因:CSS 百分比高度要求父元素有「明确 height」(不是 min-height)。
容器只有 min-height: 280px,没有 height,`.cm-editor { height: 100% }`
解析为 auto,塌缩到内容高度(3 行 ≈ 63px)。剩余 217px 透出容器背景
(paper-entry/mantle),与 .cm-editor 的 base 色形成上下割裂。
修复:改用 flexbox 而非百分比高度——
- themes.ts:`& { flex: 1 1 0; minHeight: 0 }`(替换 height:100%)
- 容器(system.rs / runner.rs):`display: flex; flex-direction: column`
flex 子项的 `flex: 1` 不依赖父元素 height,能真正填满 min-height 撑开
的空间。headless 验证:.cm-editor 现为 280px,与容器一致,割裂消失。
诊断方法:构建自包含测试 HTML(内联 editor.js),chromium --headless
--dump-dom 抓 getComputedStyle。这是应有的反馈循环,避免了前几轮
凭代码猜测导致的反复误诊。
2026-07-06 00:20:55 +08:00
3c10f72444
fix(codemirror): 编辑器未撑满容器导致上下背景割裂
...
现象:编辑器「有行号的上半部分」与「空白下半部分」背景色不一致。
根因:CodeMirror core 的 base theme 给 `.cm-editor`(&)只设了
position: relative + display: flex,没有 height。编辑器默认只占
内容高度(几行 ≈ 60px),不填满父容器。容器 div 设了 min-height:
280px,但 .cm-editor 不会自动撑到 280px——剩余空间透出容器的
paper-entry(mantle #e6e9ef) 背景,与 .cm-editor 的 base(#eff1f5)
形成「上半 base / 下半 mantle」的上下色差。
修复:themes.ts 的覆盖里加 `& { height: 100% }`,让 .cm-editor
撑满父容器。配合容器已有的 min-height: 280px,编辑器完整填充,
背景统一为 base 色,上下割裂消失。
2026-07-06 00:06:00 +08:00
64907401a0
fix(codemirror): 行号区背景与代码区割裂(core base theme 覆盖 catppuccin)
...
现象:行号列(gutter)背景是 #f5f5f5 浅灰,代码区(content)是 catppuccin
base #eff1f5 浅蓝灰,两者不一致,有明显割裂。
误诊修正:上一提交(e1b312e)以为是容器背景与 CodeMirror 层次扁平,
改了容器为 code-block(crust)。但 CodeMirror 内部 .cm-editor 和
.cm-gutters 都有不透明背景,容器色根本透不出来——所以「没效果」。
本次回退该容器改动。
真因:CodeMirror core 的内置 base theme(@codemirror/view dist index.js:6916)
给 `&light .cm-gutters` 设了 backgroundColor: "#f5f5f5" / dark "#333338 ",
特异性为 .cm-editor.cm-light .cm-gutters,高于 @catppuccin/codemirror
的 .cm-gutters 覆盖,catppuccin 的 base 背景被 core 默认灰压制。
修复:themes.ts 在 catppuccin extension 之后追加一个 EditorView.theme,
用 backgroundColor: transparent !important 强制 .cm-gutters 透明,
继承 .cm-editor 的 base 色,行号区与代码区完全融合。light/dark 同理。
2026-07-05 23:58:18 +08:00
4239602f3d
feat(codemirror): 支持 Ctrl/Cmd+Enter 运行快捷键
...
按钮一直写着「执行 (Ctrl+Enter)」但快捷键从未接线——basicSetup 不含
自定义 keymap。从底层往上打通 Mod-Enter 运行通道:
editor.ts:
- EditorOptions 加 onRunShortcut 可选回调
- constructor 条件注入 keymap.of([{ key: "Mod-Enter", ... }])
- 用 Prec.highest 包裹:@replit/codemirror-vim 的 vimPlugin 在
ViewPlugin 层拦截按键,普通 keymap 会被吞;vim 自己也用
Prec.highest,跟随此惯例保证 Mod-Enter 在 vim 之前命中
- 放在 vimCompartment 之前,满足「vim 必须在 keymap 最前」约束
- Prec 从 @codemirror/state 引入(不在 @codemirror/view)
codemirror_bridge.rs:
- EditorOptions extern 加 set_on_run_shortcut
- EditorHandle 加 _on_run_shortcut 字段保生命周期,new() 签名增加
该参数(Drop 随实例释放)
调用点适配:
- CodeRunner(runner.rs)传 no-op 闭包(不用该功能但签名要满足)
- SQL 控制台的接线在后续 system.rs 改动中完成
2026-07-05 21:31:03 +08:00
f3eb93f320
docs(agents): document code runner and fix clippy/biome lint
...
文档:
- AGENTS.md 新增「Code Runner」架构章节(三层架构 + WASM 可见性规则 +
governor 0.8 无 per_day 的处理 + runner 镜像契约)
- Environment 段补齐 CODE_RUNNER_* 与 RATE_LIMIT_CODE_EXEC_* 环境变量
Lint 修复(clippy --all-targets --all-features -D warnings 全绿):
- runner.rs/runner.rs: use_signal 冗余闭包 → 直接传 fn
- runner_config.rs: &*RUNNER_CONFIG 去 deref;assert_eq!(...false/true) → assert!
- markdown.rs: 局部变量 /// 改 //;Option.map().flatten() → and_then
- languages.rs: 删除从未读取的 LanguageDef.name 字段(语言名即 map key)
- docker.rs: start_container if let Err → ?;timeout match → is_err();
嵌套 if 合并(修 task 2 遗留 + clippy 1.96 新 lint)
- pages/admin/posts.rs: use_signal 冗余闭包(预存 lint,顺带修绿)
- codemirror editor.ts: biome format 重排 setSchema 单行
2026-07-03 18:57:25 +08:00
576803302d
refactor(editor): support dynamic languages python/javascript in codemirror
...
- editor.ts: 新增 languageCompartment + buildLanguageExtension(lang, schema)
python → lang-python, node/javascript → lang-javascript, sql/缺省 → lang-sql
新增 setLanguage(lang) 热切换; setSchema 保留当前语言不被强制切回 sql
- package.json: +@codemirror/lang-python +lang-javascript
- codemirror_bridge.rs: EditorInstance 新增 set_language wasm-bindgen extern
(产物 public/codemirror/ 被 gitignore,本地已重建)
2026-07-03 18:22:21 +08:00
90fc6977b0
feat(editor): Tiptap 编辑器配色迁移到 Catppuccin(Latte/Mocha)
...
编辑器 CSS 此前用硬编码 GitHub 风格色(#0366d6/#d73a49/#24292e 等),
独立于 paper-* 语义 token。逐元素重映射到 Catppuccin 调色板:
亮色 Latte:正文 text、标题 text、placeholder overlay0、
行内 code surface0/red、pre crust、blockquote surface1/subtext0、
hr surface1、链接 blue、selection surface0、表格 surface1/crust、
slash 弹层 mantle/surface0、checkbox/选中态 green(主强调)
暗色 Mocha:对应角色映射(text/cdd6f4 等)
上传遮罩的 rgba(255,255,255,*) 白字与 rgba(239,68,68,*) 红删除按钮
属中性覆盖层,保留不动。
2026-07-03 14:18:03 +08:00
cb77d28a68
fix(theme): 跟随系统切换改为瞬切,放弃 VT 动画
...
诊断确认:跟随系统与手动点击的 VT 流程在 JS/CSS 层完全等价(animationName、
--tt-r、bg 翻转、clip-path 全部正确),但视觉上跟随系统场景下圆形展开动画
不显示,仅瞬切。系统偏好变化是后台事件,VT 在此上下文下动画不可靠。
改用设置语义的瞬切:JS 侧新增 applyResolvedTheme(isDark) 入口(非翻转语义,
直接 add/remove dark class),Rust effect 改调 __applyResolvedTheme 传实际
明暗值,移除圆心计算逻辑。职责清晰:
- 手动点击 → __startThemeTransition → VT 圆形展开(用户手势,动画可靠)
- 跟随系统 → __applyResolvedTheme → 瞬切(后台同步语义)
2026-07-03 13:42:17 +08:00
a47e1e6027
refactor(build): workspace 根迁移至 libs/,Makefile 整合 lint/fix
...
将 pnpm workspace 根从项目根移到 libs/,项目根保持纯 Rust,JS 工作区自治:
- package.json/pnpm-workspace.yaml/pnpm-lock.yaml/biome.json 迁入 libs/
- pnpm-workspace.yaml glob 从 'libs/*' 改为 '*'(已在 libs/ 内)
- biome.json 关闭 vcs.useIgnoreFile(libs/ 无独立 .gitignore,
files.includes 已显式排除 node_modules/dist)
- Makefile 所有 pnpm/biome 调用加 'cd libs &&' 前缀
Makefile 同步整合 lint/fix(替代独立 clippy/fix target):
- make lint = biome check (libs) + cargo clippy
- make fix = biome format --write (libs) + cargo fix
- 删除 8 个 build-*-incremental/build-* target,改为 build-libs 聚合 +
build-<name> 用 pnpm --filter 单库便利 target
修复 biome 格式化回归:tiptap-editor/src/index.ts:83 的 sourceTextarea!.value
被 biome 误转为可选链 ?.,导致 TS2345(string | undefined 不匹配 string),
已还原为非空断言。
2026-07-02 18:02:52 +08:00
b1b1c88a74
chore(biome): 引入 Biome v2.5 monorepo 配置并格式化全量源码
...
新增根 biome.json(v2.5,v2 起原生支持 monorepo,子包自动向上查找):
- formatter: 2 空格缩进、单引号、行宽 100、LF、尾逗号、箭头函数括号
(匹配现有代码风格)
- linter: recommended preset + 项目化裁剪
- useTemplate/useTemplate off(geometry.ts 故意保留字符串拼接,
且测试用精确浮点断言锁定行为)
- noNonNullAssertion off(测试与 DOM 访问里的 ! 是惯用写法)
- noConsole off(4 个 lib 用 console.error/warn 做错误上报)
- CSS 关闭 noDescendingSpecificity/noDuplicateProperties
(手写 CSS 的 fallback 模式)
- useImportType/useNodejsImportProtocol/noUnusedVariables warn
- vcs.useIgnoreFile 自动读 .gitignore;排除 public/target/node_modules/
dist/.dioxus/static + Tailwind 入口 input.css(含 @theme 等 Biome 无法
解析的 at-rules)
首次格式化覆盖 29 个源文件:import 排序、node: 协议、引号/缩进统一,
并修复 index.ts 两处 forEach 回调隐式返回值(useIterableCallbackReturn)。
90 个 vitest 用例全绿,确认无语义改动。
2026-07-02 17:54:46 +08:00
79ceb307ad
chore(workspace): 建立 pnpm workspace 与共享 tsconfig
...
将 libs/ 下 4 个独立 pnpm 项目整合为 workspace:
- 新增根 package.json/pnpm-workspace.yaml,4 份重复的 devDeps
(happy-dom/typescript/vite/vitest,版本零分歧)上提到根,各 lib 只留运行时依赖
- 新增 libs/tsconfig.base.json,4 份逐字节相同的 tsconfig.json 改为 extends
- per-lib pnpm-lock.yaml 合并为根 lockfile;冗余的 libs/{lightbox,yggdrasil-core}/.gitignore 删除
(根 .gitignore 的 **/**/node_modules 已覆盖)
收益:依赖共享安装(node_modules 去重)、构建/测试可用 pnpm -r 统一调度,
单库仍可 pnpm --filter @yggdrasil/<name> 独立操作。构建与测试均验证通过(90 个 vitest 用例全绿)。
2026-07-02 17:50:52 +08:00
0772951867
fix(editor): 修复升级后空项 Enter 不退出列表(根因:畸形文档)
...
bug 表现:- [ ] 123 升级成任务列表后,空项按 Enter 不退出,而是继续新建空项。
根因有两处,都在 appendTransaction 构造新节点时:
1. stripPrefix 作用域错误:原代码 cut 作用在 listItem.content(把段落节点
当原子),cut(N) 会切进段落的标签边界,产生畸形文档——段落文本前缀残留
(如' 未完成'多出空格)、nodeSize 与实际不符。修正为作用在段落内部的
content 上(用 para.type.create 重建段落),并用 Fragment.replaceChild
替换 listItem 的第一个子节点。
2. 光标位置少算一层:+1 进 taskList 内容,+offset 到命中 taskItem,+1 进
taskItem 内容(段落节点位置),还需 +1 进段落内容(文本),原代码漏了
这最后一个 +1,导致光标落在文本中间而非末尾,splitListItem 在错误位置
分裂(把'123'拆成'12'+'3')。
畸形文档 + 光标错位 → splitListItem 在错误位置分裂,产生的'空'taskItem
实际不空,二次 Enter 走分裂路径而非退出路径。
新增回归测试:段落文本无残留 + 完整 Enter 链路(升级→Enter 空项→Enter 退出)。
2026-07-02 15:27:37 +08:00
53b53d3169
fix(editor): appendTransaction 加重入防护 + Enter 行为测试
...
用户反馈:升级成 taskList 后,空项按 Enter 未退出列表(与斜杠命令创建的
行为不一致)。
单测用 splitListItem 命令验证:升级后的 taskList,有内容项 Enter 分裂出空
taskItem,空项 Enter 正确退出列表(doc 末尾产生 paragraph)。行为与斜杠
命令创建的一致。说明 appendTransaction 产生的节点结构合法。
追加防护:给 appendTransaction 返回的 transaction 打 pluginKey 标记,
重入时跳过,避免 Enter 等操作的文档变化被本插件二次干扰。
happy-dom 无法可靠模拟真实 keydown 链路(派发 KeyboardEvent 时光标映射
不准),Enter 的 bug 需真实浏览器复现。
2026-07-02 14:59:53 +08:00
873ccb1a80
fix(editor): task list label 内 checkbox 居中
...
label 默认是 inline 元素,内部 checkbox(inline-block)受父级行高影响会产生
基线间距,导致 checkbox 在 label 盒子内偏上,即便 li 用了 align-items: center
也只对齐了 label 盒子而非 checkbox 本身。
给 label 加 display: flex + align-items: center + line-height: 0,消除行高
撑出的基线空间,让 checkbox 严格居于 label 盒子中心。
2026-07-02 14:42:52 +08:00
4e96243407
style(editor): 任务列表 checkbox 与文本垂直中线对齐
...
与前台 input.css 同步,编辑器内 task list 同样改为 align-items: center。
- li: flex-start → center,实现真正的中线对齐
- label: 去掉 margin-top: 0.3em(center 已处理)
- 新增 li > div > p { margin: 0 }:task list 项内段落清掉默认 .6em 0
上下 margin,否则 margin 撑高 div 导致 checkbox 与文本错位、项间距过大
2026-07-02 14:38:59 +08:00
250fe653c1
fix(editor): 修复任务列表升级后光标被甩到下一行
...
appendTransaction 用 replaceWith 整段替换 bulletList→taskList,ProseMirror 的
选区映射无法把原 listItem 内的光标正确映射到新 taskItem 内,默认落到替换
区域之后(下一行)。
显式用 TextSelection.near 把光标设到命中的 taskItem 段落文本末尾:
按命中项在 newItems 中的索引 + 段落文本长度算出目标 pos。
新增光标位置断言(selection.from === 3,即 doc>taskList>taskItem>paragraph 内)。
2026-07-02 14:25:57 +08:00
4a27236859
fix(editor): TaskInputRule 改用 appendTransaction(逐字符输入才生效)
...
input rule 方案对逐字符输入无效:BulletList 在用户打 - (第2字符)时就抢先
触发,把行变成 bulletList>listItem,之后 [ ] 在 listItem 内无法被任何
input rule 升级。提高 priority 也无济于事——BulletList 在更短文本上抢先。
改用 appendTransaction 监听文档变化:BulletList 触发后,用户在 listItem 里
打出 [ ] / [x] 前缀时,把该 bulletList 整体替换成 taskList,对应 listItem
转成带 checked 的 taskItem 并删除已识别的文本前缀。
- 测试模拟逐字符时序:先 - (applyInputRules 触发 BulletList),再 [ ]
(appendTransaction 升级),5 个用例覆盖未勾选/已勾选/大写/不误判/星号
- * + - 三种 marker 的 bulletList 都会升级(BulletList 对三者创建同种节点)
2026-07-02 14:16:34 +08:00
4a76397af8
feat(editor): 支持 - [ ] 手动输入创建任务列表
...
Tiptap 官方 TaskItem 的 input rule 只匹配 [ ] 不含 marker,且 StarterKit
的 BulletList input rule /^\s*([-+*])\s$/ 在用户打 - 时就抢先触发,把这行
变成无序列表;后续的 [ ] 沦为字面文本,getMarkdown 序列化时转义成 \[ \],
前台无法渲染成 checkbox。
- 新增 TaskInputRule 扩展,priority 1000 抢在 BulletList(默认 100)之前,
正则 /^(\s*-\s)\[( |x|X)\]\s$/ 匹配 - [ ] / - [x],
wrappingInputRule 包裹成 taskList > taskItem(findWrapping 自动补齐两层)
- 仅识别减号 marker(* + 仍走 BulletList),Enter 续行复用 TaskItem 内置
splitListItem,不重复实现
- 新增 task-input-rule 测试(真实 Editor + happy-dom):覆盖未勾选/已勾选/
大写 X/不误判普通列表/星号 marker
2026-07-02 13:56:11 +08:00
c74e59485d
feat(editor): add codemirror-editor subproject + Rust bridge
...
新建 libs/codemirror-editor(pnpm/Vite IIFE/@catppuccin/codemirror Catppuccin
Latte+Mocha 主题/@codemirror/lang-sql schema 补全/@replit/codemirror-vim),
输出 public/codemirror/。Rust 桥接 codemirror_bridge.rs 镜像 tiptap_bridge.rs
(Reflect::get 取对象字面量 + EditorHandle 持有闭包 + Drop→destroy)。
接入 Makefile/Dioxus.toml/.gitignore。
注意:thememirror 不含 catppuccin 主题,改用官方 @catppuccin/codemirror@1.0.3
(导出 catppuccinLatte/catppuccinMocha)。
2026-06-29 18:24:38 +08:00
8ec4ecd310
chore: migrate JS subprojects from npm to pnpm
...
CI / check (push) Failing after 4m41s
CI / build (push) Has been skipped
Switch tiptap-editor, lightbox, and yggdrasil-core from npm to pnpm.
Replace package-lock.json with pnpm-lock.yaml and update Makefile accordingly.
2026-06-29 10:35:04 +08:00
225c477f8c
chore(theme): remove debug prints
2026-06-26 18:27:27 +08:00
065302544d
fix(theme): always expand NEW layer and explicitly set html background
...
When turning Dark off (Dark -> Light transition), the NEW layer is Light.
Previously we tried shrinking the OLD layer (Dark) over it, but if the
root snapshot captured a transparent html, revealing the transparent layer
produced no visual change.
Now we:
1. Always keep the NEW layer on top (z-index: 2).
2. Always animate the NEW layer expanding from 0 to max radius.
3. Explicitly set background-color on html in input.css to ensure the
VT root snapshots are fully opaque, avoiding transparent captures that
make expansions invisible.
4. Add background: var(--color-paper-theme) directly to ::view-transition-new
just to be absolutely certain it's painted opaque.
2026-06-26 18:17:13 +08:00
93d3858a37
chore(deps): update package-lock.json
2026-06-26 18:15:00 +08:00
0ff24888ce
refactor(theme): use pure CSS View Transitions without WAAPI or style injection
...
Adopt a fully CSS-driven strategy for the theme transition:
- Use CSS variables (--tt-x, --tt-y, --tt-r) injected onto documentElement
- Define static @keyframes in style.css using these variables
- Key logic: the Dark layer is ALWAYS placed on top (z-index: 2).
- Light -> Dark: NEW layer is Dark, so NEW expands from circle(0) to cover OLD.
- Dark -> Light: OLD layer is Dark, so OLD shrinks to circle(0) to reveal NEW.
This completely eliminates reliance on Web Animations API (which had bugs
with clip-path when animation:none is set) and eliminates dynamic <style>
injection (which caused timing gaps and style residue).
The result is perfectly symmetrical and bug-free in both directions.
2026-06-26 18:14:29 +08:00
76eedebf05
fix(theme): remove animation:none that blocks WAAPI on VT pseudo-elements
...
Chrome does not allow WAAPI clip-path animations on ::view-transition-new
when animation:none !important is set via CSS. This caused dark→light
transitions to have no visible animation.
Replace with opacity:1 !important to neutralize the UA fade-in/out
animations: the UA animations still 'run' but cannot change opacity
because !important author declarations override CSS animation values.
WAAPI clip-path is no longer blocked.
2026-06-26 18:11:16 +08:00
0f9c8fb8a3
fix(theme): lock VT pseudo-element opacity/blend via CSS !important
...
Gray expanding circle was caused by UA default animations (fade-in/out)
and mix-blend-mode: plus-lighter not being fully overridden by WAAPI
opacity animations alone.
Fix: use CSS with !important to hard-lock the VT pseudo-elements:
- animation: none !important — kills UA fade-in/out
- opacity: 1 !important — both layers stay fully opaque
- mix-blend-mode: normal !important — no color addition
WAAPI now only controls clip-path on ::view-transition-new(root),
which doesn't conflict with any CSS property (no CSS clip-path exists).
This cleanly separates concerns: CSS handles static properties,
WAAPI handles the dynamic circle expansion.
2026-06-26 18:08:28 +08:00
f0ea1a294f
refactor(theme): switch VT animation from CSS injection to Web Animations API
...
Replace dynamic <style> injection (@keyframes + pseudo-element rules)
with document.documentElement.animate({ pseudoElement }) calls.
Previous approach accumulated <style> elements across transitions,
causing subsequent dark→light animations to be invisible. The Web
Animations API:
- Creates per-call Animation objects with no DOM residue
- Has higher composite priority than CSS animations, naturally
overriding UA default fade-in/out without !important
- Eliminates style element lifecycle management entirely
Static CSS now only sets mix-blend-mode: normal (not animatable via
WAAPI) and the is-theme-transitioning transition suppression.
2026-06-26 18:02:26 +08:00
b542952619
fix(theme): pre-inject VT styles before startViewTransition
...
Previous approach injected ::view-transition-new animation in vt.ready,
creating a timing gap where the NEW layer had no clip-path and was fully
visible, making dark→light transitions invisible.
Now:
- Inject <style> with @keyframes + VT pseudo-element rules BEFORE
calling startViewTransition, so styles are ready when pseudo-elements
are created.
- Force synchronous getComputedStyle() in VT callback to ensure the
NEW snapshot captures final background-color, not a mid-transition
value.
2026-06-26 17:55:45 +08:00
ba9db2d75a
fix(theme): fix dark→light transition animation invisible
...
The static CSS rule 'animation: none' on ::view-transition-new(root)
caused the new (light) screenshot to be fully visible before the JS-
injected clip-path @keyframes kicked in. This meant the light layer
was already covering the dark layer, making the expanding circle
invisible.
Fix: move all VT pseudo-element styles (animation, mix-blend-mode)
into the dynamically injected <style> block in vt.ready, so they
apply atomically with the clip-path keyframes.
Also:
- Add transition:none !important during VT to prevent body's
background-color 0.3s transition from producing a stale capture.
- Restore Dioxus theme.set() that was disabled for debugging, so
the toggle icon and localStorage persist correctly.
- Use !important on injected animation to reliably override any
UA default VT animations.
2026-06-26 17:50:45 +08:00
c1781d7831
fix(theme): VT 期间冻结 CSS 过渡,让圆形展开可见
...
body 有 transition: background-color .3s,VT 在 toggle dark class
后下一帧拍 NEW 快照时背景色才走了 ~5%,新旧快照几乎一样,
圆形展开看不出颜色差。修复:回调里加 vt-freeze 类禁用所有
过渡,让 NEW 快照立即拿到新主题配色;动画结束(finished)后
移除。补 1 个测试覆盖 freeze 生命周期。
2026-06-26 14:36:41 +08:00
6979bd1010
feat(theme): 圆形展开主题切换动画(View Transitions API)
...
点击主题按钮时,新主题从按钮位置以圆形向外展开覆盖全屏。
JS 同步 toggle dark class 拍快照 + CSS clip-path circle keyframes;
Rust theme.set 事后对齐状态(use_effect 幂等)。
reduced-motion 与不支持 VT 的浏览器自动降级为瞬切。
2026-06-26 14:06:23 +08:00
ac1f92d816
refactor(core): 迁移 post-content 到 yggdrasil-core,删除 public/js
...
逐行 TypeScript 化(逻辑不变),改走 window.__initPostContent 全局入口。
post-content.js 已迁移进 libs/yggdrasil-core/,public/js/ 目录随之删除。
2026-06-26 13:57:48 +08:00
afabdf4655
chore(core): 搭建 yggdrasil-core 子工程骨架
...
照搬 lightbox 的 Vite IIFE 工程结构,作为核心 JS 统一归处。
此 commit 仅打通构建管线,业务逻辑后续 task 填入。
同时把 public/yggdrasil-core 加入根 .gitignore。
2026-06-26 13:51:39 +08:00
2f861d3e6a
chore(lightbox): update package-lock.json
CI / check (push) Failing after 5m8s
CI / build (push) Has been skipped
2026-06-24 15:57:47 +08:00
25eacc1c3c
refactor(lightbox): modernize to const/let, template literals, drop IIFE
...
纯现代化重构,行为不变(23 个回归测试全绿作证):
- var → const/let(state、gotoIndex 的 newIndex 用 let,其余 const)
- 循环 for+var+IIFE包装 → for..of/forEach + const(天然捕获迭代变量,
消除旧 var 循环闭包必须立即执行函数固定变量的写法)
- 字符串拼接 → 模板字面量(counter 文本、transform、width/height)
- 事件处理 function() → 箭头函数(无 this 依赖场景)
- 拆掉外层 (function(){'use strict'; ... })():ES module 作用域已封装,
IIFE 是历史遗留;辅助函数仍用 function 声明(openLightbox↔closeLightbox
互递归依赖提升)
几何纯函数(fitCentered/transformFor/originalUrl)已在 geometry.ts,
本文件只剩 DOM 胶水 + state 单例。
2026-06-24 11:44:06 +08:00
aadfed2595
test(lightbox): add happy-dom behavior regression tests for refactor
...
为现代化重构(var→const、拆 IIFE、for→for..of)补行为回归防线。
黑盒驱动 window.__initLightbox,覆盖高风险路径:
- 循环闭包捕获 idx(点第 1/2/3 张 counter 显示正确序号)
- 点击打开(overlay 创建、origSrc 去 query、caption、单张 counter 隐藏)
- 关闭(Esc / 点背景;点图片本身不关)
- 图集 gotoIndex 循环边界(首末衔接 + originNode 更新后焦点归还)
- 单张图不参与切换
happy-dom 20.10.6 作 devDep,vitest 默认环境改 happy-dom;
geometry.test.ts 用 // @vitest-environment node 指令保持纯函数在 node 跑。
23 tests (12 geometry + 11 behavior) 全绿。
2026-06-24 11:40:36 +08:00
507d271a2b
fix(lightbox): prevent page scroll on close via focus({ preventScroll: true })
...
关闭灯箱时 removeOverlay() 调用 .focus() 归还焦点给原图,浏览器的 focus()
默认会 scrollIntoView,导致页面自动滚动把原图完整纳入视口。用户向下滚动后
只点露出的下半截图、再 Esc 关闭时,页面会向上跳到整图可见——非预期行为。
focus({ preventScroll: true }) 抑制该滚动(Chrome68+/FF68+/Safari15.4+ 支持),
焦点归还的无障碍语义不变。
2026-06-24 11:27:15 +08:00
b4ef906141
refactor(lightbox): move .lightbox-* CSS into lightbox project, wire import
2026-06-24 11:02:44 +08:00
5f57d375ea
feat(lightbox): migrate DOM logic to TypeScript index.ts with self-start contract
2026-06-24 10:57:23 +08:00
44fc641aaa
feat(lightbox): extract geometry pure fns with vitest coverage
2026-06-24 10:52:48 +08:00
c5af4457e2
feat(lightbox): scaffold libs/lightbox TS project (vite + tsc + vitest)
2026-06-24 10:49:58 +08:00
fb31ecb5ae
fix(tiptap): expose EditorOptions as runtime class for wasm-bindgen
...
EditorOptions was a TS interface (erased at runtime), but the Rust
wasm-bindgen extern uses #[wasm_bindgen(constructor)] which generates
'new EditorOptions()' — requiring a global constructor.
Vite 5/Rollup accidentally leaked the name into IIFE global scope,
masking this. Vite 8/Rolldown strictly erases interfaces, exposing it
as 'EditorOptions is not defined'.
Fix: make EditorOptions a class (retains runtime constructor) and mount
it on window, since the IIFE bundle name is taken by TiptapEditor.
2026-06-24 10:27:07 +08:00
08c2539dfc
test(tiptap): add isValidUrl tests covering scheme validation edge cases
2026-06-24 10:10:39 +08:00
90cff2d0cc
test(tiptap): add UploadImageNodeView tests for rendering/update/callbacks/destroy
2026-06-24 10:09:36 +08:00
f7042d3ab4
test(tiptap): add UploadCoordinator tests for counts/lifecycle/emit/idempotency
2026-06-24 10:07:43 +08:00
8ff051689f
refactor(tiptap): export isValidUrl and UploadImageNodeView for testability
2026-06-24 10:04:59 +08:00
19532670d3
build(tiptap): add vitest config with happy-dom environment
2026-06-24 10:04:23 +08:00
21c0359f5e
build(tiptap): upgrade to vite 8/vitest 4/typescript 6/tiptap 3.27, migrate to rolldownOptions
2026-06-24 10:03:47 +08:00