728 Commits

Author SHA1 Message Date
xfy
23f0cc6222 fix(ui): adjust search placeholder size and position 2026-06-29 14:10:21 +08:00
xfy
b366c00e74 fix(ui): enlarge and better center search placeholder image 2026-06-29 14:07:35 +08:00
xfy
b666472193 fix(ui): add margin between post count and list in tag detail page 2026-06-29 14:05:47 +08:00
xfy
3f7d112898 feat(pages): add empty placeholder image to search page 2026-06-29 13:55:08 +08:00
xfy
528a79fb83 feat(pages): add empty state to tags and tag detail pages 2026-06-29 13:50:05 +08:00
xfy
373498870a style: apply cargo fmt to workspace
Some checks failed
CI / check (push) Failing after 5m35s
CI / build (push) Has been skipped
2026-06-29 13:47:40 +08:00
xfy
5e1becf116 fix(admin): explicitly set pointer-events auto for trash settings panel to resolve click bug 2026-06-29 13:46:05 +08:00
xfy
ef8af77a08 style(admin): format trash.rs 2026-06-29 13:42:29 +08:00
xfy
fdaefae027 test: fix test_auto_purge_settings_has_transition_class false positive 2026-06-29 13:37:59 +08:00
xfy
768dbc9510 feat(admin): add smooth slide-down animation to trash auto-purge settings panel 2026-06-29 13:34:43 +08:00
xfy
a47a5a6ac9 feat(admin): update empty state for trash page 2026-06-29 13:26:45 +08:00
xfy
c69a632e75 fix(ui): trigger FilterTabs indicator animation on click 2026-06-29 13:24:52 +08:00
xfy
3daa09efc2 fix(ui): fix WASM compilation error in FilterTabs indicator animation 2026-06-29 13:22:22 +08:00
xfy
ec5f933191 feat(ui): add smooth sliding animation to FilterTabs active indicator 2026-06-29 13:20:39 +08:00
xfy
d91a56f8b2 refactor(admin): extract comments filter tabs into FilterTabs component 2026-06-29 13:15:50 +08:00
xfy
79af7d345e fix(admin): 修复重建内容后标题行纵向错位
点击「重建内容 / 重建全部」后,结果消息撑高了 RebuildCacheBar,外层
header 的 items-center 据此重新居中,把「文章管理」标题与「+ 写文章」按钮
整体下移,而重建按钮停在容器顶部,造成三者不再同处一行。

根因:c024de1 抽取 RebuildCacheBar 时把结果消息纳入子组件的 flex-col 文档流
(重构前消息是页面级兄弟节点,不参与 header 居中计算)。

修复:根容器改 relative,结果消息用 absolute 悬挂于按钮行下方、脱离文档流,
组件盒高恒等于按钮行高度,无论消息是否出现都不影响 header 的垂直居中。

验证:dx check + cargo clippy 全绿。
2026-06-29 11:49:28 +08:00
xfy
a1aef5bda9 fix(admin): add cursor-pointer to comment status filter buttons 2026-06-29 11:41:52 +08:00
xfy
dbe95f1173 feat(admin): use global EmptyState component in comments management page 2026-06-29 11:39:32 +08:00
xfy
72d21c6670 feat(admin): use global EmptyState component in posts management page 2026-06-29 11:38:50 +08:00
xfy
46e9a29f1e fix(theme): 消除 ThemeToggle 在 wasm/server 构建的 unused_mut 警告
Some checks failed
CI / check (push) Failing after 6m25s
CI / build (push) Has been skipped
dx build 的 wasm target 报两条 unused_mut:

- theme (160行): wasm 端的 theme.set 被非 wasm cfg 分支剥离,写操作转移
  到副本 theme_clone,theme 本身在 wasm 下只被读取/Copy。加
  #[cfg_attr(target_arch = "wasm32", allow(unused_mut))]——仅 wasm 端压住,
  server 端仍需 mut(theme.set(next) 实际存在)。
- gen_clone (191行): 闭包内只读 gen_clone(),从未 set,两端都不需 mut,
  直接删除。

顺带把原来无条件挂在 click_gen 上的 #[allow(unused_mut)] 改成
#[cfg_attr(not(target_arch = "wasm32"), ...)]——click_gen 只在 wasm 分支被
set(189行),server 端该分支被剥离。改为按 cfg 方向标注,与 theme 对称,
语义更精确(不再无差别压住 wasm 端的检查)。

匹配项目既定模式(post_cover.rs:20 的 cfg_attr allow)。零行为变更,
dx check / cargo check(双 target) / clippy / theme 测试均通过。
2026-06-29 11:31:57 +08:00
xfy
b923851284 docs(main): 注明 CompressionLayer 默认跳过 image/*
Some checks failed
CI / check (push) Failing after 5m37s
CI / build (push) Has been skipped
避免后续误以为压缩层会对 WebP/PNG/JPEG 等已是压缩格式的图片再次
压缩而浪费 CPU。tower-http 的 DefaultPredicate 开箱即跳过 image/*
(例外 image/svg+xml)、gRPC、SSE 及 <32 字节响应,无需额外配置。

纯文档性注释,无行为变更。
2026-06-29 11:16:30 +08:00
xfy
74f8c212f6 feat(csrf): warn at startup when APP_BASE_URL is unset
APP_BASE_URL 未设置时 trusted_origin 回退到请求 Host 头推导本站 origin,
反向代理后若 Host 头可被客户端影响存在 CSRF 绕过风险。.env.example 虽然
警告了,但默认值就是空——'cp .env.example .env 后忘改' 是最常见的部署失误。

加启动时一次性 WARN(与 image.rs 的启动告警同范式):
- warn_if_app_base_url_unset() 在 main.rs 启动序列调用,与 validate_database_url
  等配置告警归在一处
- 抽出纯函数 app_base_url_is_set() 承载判断逻辑,便于测试,打日志副作用与之解耦
- 5 个单测覆盖 unset/empty/whitespace/set/trim 边界

每请求路径的 trusted_origin 保持纯函数不变,不引入 dedup 状态或刷屏风险。
2026-06-29 11:02:58 +08:00
xfy
cb73a9b979 chore: fix cargo clippy warnings (unused import and false positive unused_mut) 2026-06-29 10:14:27 +08:00
xfy
178870427c feat(ui): refactor EmptyState to accept dynamic string props and add empty state to archives page 2026-06-29 10:11:00 +08:00
xfy
82a8c511fc fix(theme): remove dark class from AppRouter div 2026-06-26 18:24:00 +08:00
xfy
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
xfy
1e78c7f0f9 fix(doc): 修复 /doc 路由与 Dioxus 静态托管冲突的 panic
Dioxus 在 dev 用 nest_service("/doc", ServeDir) 托管 public/doc/ 目录,
手动在 static_routes 注册 /doc 会在 merge 时触发路由冲突 panic。

根因(dioxus-server serve_dir_cached):dev 模式对 public/ 下的目录用
nest_service 挂整个 ServeDir,注册了 /doc;我在 static_routes 加的
精确 /doc 路由与之同名冲突。

修复:
- main.rs: 移除手动注册的 /doc 与 /doc/ 重定向路由,交给 Dioxus 托管。
  文档深层路径 /doc/yggdrasil/* 由 ServeDir 直接服务。
- Makefile: doc 目标拷贝后额外生成 public/doc/index.html 重定向页,
  ServeDir 访问目录根时返回它,经 meta refresh + JS 跳转到
  yggdrasil/index.html,让裸路径 /doc 也能直达文档。
- build/clean 同步集成 make doc 与清理 public/doc。
2026-06-26 14:27:21 +08:00
xfy
30a861f910 docs(agents): 新增 yggdrasil-core 子工程章节
与 lightbox/tiptap-editor 并列,记录用途、构建、注入方式与
主题展开动画的 View Transitions 实现。同步更新 make test 注释
与 Build Artifacts 列表。
2026-06-26 14:09:46 +08:00
xfy
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
xfy
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
xfy
25e01a109b fix(write): 移除 cover_uploading 多余的 mut 绑定
本组件只读取(on_submit 拦截)与传递给 CoverUploader,写入全在子组件
内部。Signal 是 Copy 类型,.set() 不要求 mut 绑定,编译器的 unused_mut
警告是真阳性。
2026-06-26 11:07:31 +08:00
xfy
33db6dc5aa feat(doc): 文档托管到 /doc 路径
- main.rs: static_routes 加 /doc 与 /doc/ 的 301 重定向到
  /doc/yggdrasil/index.html,绕开 Router 的 /:..segments 兜底。
  文档深层路径由 Dioxus 自动托管 public/ 提供,无需额外代码。
- .gitignore: 忽略 public/doc 构建产物(配合 make doc 拷贝)
2026-06-26 11:04:29 +08:00
xfy
c024de1b09 refactor(posts): 抽取 RebuildCacheBar 子组件,降低 PostsPage 复杂度
PostsPage 主体从约 181 行降至约 121 行。「重建内容 / 重建全部」工具条(两个按钮
+ 状态信号 rebuilding/rebuild_result + do_rebuild 异步闭包 + 结果消息)整体迁入
RebuildCacheBar 子组件,完全自洽,无任何 prop。

布局调整:原结果消息是页面级兄弟节点,现纳入子组件垂直容器(按钮行在上、消息
在下),保持「消息显示在按钮下方」的视觉语义;+ 写文章 链接仍与工具条在标题行
水平并排。

依据 dioxus-render-purity skill 的 god component 治理建议。验证:cargo build +
dx check + cargo test (406) 全绿。
2026-06-26 10:50:29 +08:00
xfy
a534ee436f refactor(trash): 抽取 AutoPurgeSettings 子组件,降低 TrashPage 复杂度
TrashPage 主体从约 484 行降至约 266 行。自动清理配置(可折叠设置面板:
开关 + 保留天数步进 + 保存 + 已保存反馈)整体迁入 AutoPurgeSettings 子组件,
含 6 个私有 signal(draft_days/enabled、panel_open、saving、just_saved)+ dirty
memo + chevron_rotate 派生。

接口:settings(已保存配置)作为双向绑定 signal prop——子组件加载/保存时写入,
父组件读取 retention_days 供 TrashRow 的「剩余天数」展示。比 CoverUploader 的
两 signal 接口更简。

附带简化:原 settings_loaded 守卫删除——子组件挂载即触发一次自身 effect 加载
配置,不再嵌入列表翻页 effect,逻辑更清晰。

依据 dioxus-render-purity skill 的 god component 治理建议。验证:cargo build +
dx check + cargo test (406) 全绿。
2026-06-26 10:31:23 +08:00
xfy
aa3bbdc522 docs(pages): 补充 pages 模块文档
最后一个缺模块文档的文件,至此 cargo doc 三维度全满:
371 pub 项全覆盖 / 122 文件模块文档全覆盖 / 零 rustdoc 警告
2026-06-26 10:05:40 +08:00
xfy
f342742820 docs(write): 清理 CoverUploader 重复/失实的文档注释
最终 review 指出 c2eb261 留下了一段重复且失实的 doc(仍声称 cover_uploading
对本组件私有,与已改为双向绑定 prop 的事实矛盾)。删除旧段落,保留正确描述
两个双向绑定 signal 的版本。
2026-06-26 09:57:15 +08:00
xfy
c2eb2614bb fix(write): 恢复 cover_uploading 上传中保存拦截
code review 指出上一提交(835ada0)移除 on_submit 的 cover_uploading()
拦截是真实回归:uploads_in_flight 只覆盖编辑器内图片上传,不含封面上传
路径(upload_image_file 是独立 async,不更新该计数)。用户选大图后立即
点保存会把空/旧封面落库,封面被静默丢弃。

修复:cover_uploading 改由父组件 write_editor 声明并作为 prop 传入
CoverUploader(与 cover_image 同为双向绑定 signal),恢复 on_submit 的
上传中拦截。子组件仍负责 set,父组件读,接口从 1 个 signal 扩为 2 个
(cover_image + cover_uploading),均为父组件真正需要的状态。
2026-06-26 09:52:55 +08:00
xfy
835ada094d refactor(write): 抽取 CoverUploader 子组件,降低 god component 复杂度
write_editor 从 746 行降至约 480 行。封面上传(5 个私有 signal + 上传闭包 +
封面 rsx)整体迁入 CoverUploader 子组件,仅通过 cover_image signal 双向绑定
与父组件通信。依据 dioxus-render-purity skill 的 god component 治理建议。

附带调整:
- META_LABEL_CLASS / META_INPUT_CLASS 提升到模块级常量,供 write_editor 与
  CoverUploader 共用(原为 write_editor 函数局部 const,CoverUploader 取不到)。
- 移除 on_submit 中的 cover_uploading() 上传中拦截:该 signal 已私有化到子组件,
  且上传耗时极短(前端并发上传有 uploads_in_flight 拦截兜底),收益不抵跨组件
  暴露 signal 的复杂度。

验证:cargo build + dx check + cargo test (406) 全绿。
2026-06-26 09:42:14 +08:00
xfy
afebd441ae refactor(trash): dirty 派生值改用 use_memo
依据 dioxus-render-purity skill 规则二:派生值不应在 render 期内联重算。
dirty 是 settings_draft_* 与 settings 的纯派生布尔值,改用 use_memo,
依赖信号不变时跳过 trim/parse 重算。同步更新两处使用点为 dirty() 调用。
2026-06-26 09:33:13 +08:00
xfy
96da2f27d5 docs: 补齐 tiptap_bridge 文档注释并新增 make doc 目标
- tiptap_bridge.rs: 为 wasm-bindgen 桥接层全部 19 处 pub 项补文档
  (JS 类型映射、getter/setter、EditorHandle::new、pub use 重导出)
- markdown.rs: 用反引号包裹注释内字面量 <span>,消除 rustdoc 警告
- Makefile: 新增 doc / doc-open,纯 binary crate 需 --document-private-items
  才能让内部模块进文档
2026-06-25 18:26:56 +08:00
xfy
225bb2406f docs(skill)+fix: 新增 dioxus-render-purity skill 并修复 post_detail 渲染期 set signal
依据 Dioxus 0.7 antipatterns 官方指南,做两件事:

1. 新增 skill .agents/skills/dioxus-render-purity/
   把「render 函数必须纯净」做成可复用 skill,列出三类反模式(render 期
   副作用 / 用 use_signal 存派生值 / 用 use_effect 算派生值)及自检清单,
   防止后续编辑(人或 AI)再次在组件里写 signal.set 等副作用。触发关键词
   覆盖 dioxus/component/rsx/signal/use_effect 及对 src/pages、src/components
   的编辑。

2. 修复 post_detail.rs 的真实违规(被该 skill 捕获的典型反例):
   原代码用 use_signal 镜像 slug prop,再在 render 期 if slug_signal()!=slug
   { slug_signal.set(...) } 触发 server future 重跑——这是文档明确点名的
   「don't mutate state during render」。改为直接读 prop:
   use_server_future(move || get_post_by_slug(slug.clone())),路由 slug 变化
   时 Dioxus 自动重跑 future,无需镜像信号。key 改用 post.slug(post 已在
   match 分支内可用,避免 slug 被 move 后再读)。同步更新模块文档注释。

验证:dx check 通过、cargo test 405 passed。
2026-06-25 18:24:57 +08:00
xfy
9a00972be4 fix: 修复格式化工具破坏的单行 if/else 与闭包语法
格式化工具在展开单行语句时引入了 5 处语法错误,导致 dx serve 无法编译:

- comments.rs / trash.rs:on_select 的 `if checked { insert } else { remove }`
  丢失 else 分支,变为悬空语句 + 括号不匹配。
- trash.rs(3 处):`web_sys::window().and_then(|w| {...}).unwrap_or(false) {`
  展开闭包体时漏掉闭合 `)`,吞掉了 if 块的开括号。
- write.rs:封面图 `if cv.contains('?') {...} else {...}` 丢失 else,
  多出一个 `}`,导致 src 块括号失衡。

均还原为正确的多行 if/else 结构。验证:dx check 通过、cargo test 405 passed。
2026-06-25 18:11:35 +08:00
xfy
0398cc6c66 perf(render): 列表加显式 key + release 启用 panic=abort
依据 Dioxus 0.7 optimizing 指南做两项改进:

- 为数据驱动列表渲染补全显式 key(13 处,9 个文件):
  home/archives/tags/search 文章与标签列表、post_card/post_footer
  标签、header 桌面/移动导航、admin dashboard/posts/write 列表。
  使用各类型稳定唯一字段(post.id / tag / tag.name / item.label /
  err.id / year_group.year / month_group.month_en)作为 key,
  让 Dioxus diff 正确识别元素身份。骨架屏 for _ in 0..N 静态占位
  循环与已带 key 的评论树/admin 行不动。

- [profile.release] 加 panic = "abort":WASM 去掉 unwind 元数据减小
  体积;server 端错误处理走 Result+?(见 error.rs),不依赖 panic
  unwind,无副作用。

其余文件为本机格式化(单行折叠/多行展开),一并提交。

验证:dx check 通过、cargo test 405 passed。
2026-06-25 18:07:22 +08:00
xfy
c836e3e1df feat(api): 新增 healthz/readyz 健康检查端点
Some checks failed
CI / check (push) Failing after 7m29s
CI / build (push) Has been skipped
新增两个无中间件探针端点,供 Docker HEALTHCHECK 与反向代理/负载
均衡使用:

GET /healthz — liveness 存活探针,进程在跑即 200,不查 DB。
GET /readyz  — readiness 就绪探针,SELECT 1 检测 DB 连通性(2s
超时),不可达返回 503,附带连接池 size/available/max_size/waiting
指标。

路由挂载在 static_routes(无 CSRF/超时/缓存中间件),避免被
add_cache_control 误加 max-age 缓存头导致探针误判。readyz 直接用
DB_POOL.get() 不走 get_conn 的退避重试,保证探针快速失败。

dx check + clippy + 405 tests 全通过。
2026-06-25 16:45:09 +08:00
xfy
d41eb4b3cd refactor(write): 滚动条从内容区内部移到窗口边缘
将 /admin/write 从「锁一屏 + 内容区内部滚动」改为「整页自然滚动」,
滚动条出现在浏览器窗口右侧而非中间内容区边缘。

admin_layout: write 路由 root 由 h-dvh overflow-hidden 改为 min-h-dvh
自然文档流,main 去掉 overflow-hidden。

write.rs: 根 div 与内容区去掉 flex-1/min-h-0/overflow-y-auto 等滚动
约束;编辑器高度由 flex-1 改为 h-[60vh](min-h-[400px] 兜底);底部
操作栏去掉 flex-shrink-0,改为跟随页面滚动。

整页滚动时 Header 仍 sticky 吸顶(带毛玻璃),保存栏随内容滚走。
2026-06-25 16:24:26 +08:00
xfy
7611c60e6c feat(write): 保存按钮改用鼠尾草绿主色
Some checks failed
CI / check (push) Failing after 5m21s
CI / build (push) Has been skipped
write 页的「保存」主按钮原为石墨黑 bg-paper-primary,与 dashboard
「写文章」等主操作按钮颜色不一致。改为 bg-paper-accent 鼠尾草绿,
统一后台所有主操作按钮配色。hover 改用 brightness-110 + active
scale 与其它主按钮交互手感一致。禁用态(saving)保持 paper-tertiary 灰。
2026-06-25 15:14:31 +08:00
xfy
806b474599 feat(theme): 新增冷调玫瑰第二色用于次要按钮
在 input.css 的 @theme 新增 accent-2 / accent-2-soft 主题变量,
用于与鼠尾草绿主色成对的次要操作按钮。

颜色选择经 HSL 分析:色相 342°(冷调玫瑰,区别于暖砖红 9°),
饱和度 28%(低饱和从属于主色),保证视觉为浅色系且不浊。
对比度:亮色 #83495b vs 米白底 6.5:1、暗色 #cca4b0 vs entry 底 7.46:1,
均过 WCAG AA。

新增 BTN_SECONDARY 常量(ghost 描边风格)用于 dashboard「管理文章」
按钮,与「写文章」实心主按钮构成主次层级。两按钮统一用
inline-flex items-center justify-center 保证文字水平垂直居中、等高。
2026-06-25 15:06:45 +08:00
xfy
519c0ae3a9 feat(admin): 后台迁移至 paper-* 主题变量对齐前台
将 /admin 路由下硬编码的 Tailwind gray + hex 配色统一迁移到
input.css 定义的 paper-* 主题变量系统,使后台视觉完全对齐前台:

- 底色:bg-white → bg-paper-theme(米白,与前台一致)
- 卡片/表格:bg-white dark:bg-[#2e2e33] → bg-paper-entry
- 主操作按钮(写文章/保存/分页):bg-gray-900 黑 → bg-paper-accent 鼠尾草绿
- 文字/边框/hover:gray-* + dark:hex → text-paper-*/border-paper-*
- 清理 trash 页散落的 #5c7a5e/#7da97f/#e8f0e8 等 hex,路由到变量
- 骨架占位统一复用 ADMIN_* 常量与 SkeletonBox

保留 green/amber/red 状态语义色(通过/待审/删除)与 toggle 白色滑块。
统一采用 bg-paper-* 工具类语法,与 header/post_card 主流写法一致。

dx check + clippy + 402 tests 全通过。
2026-06-25 14:33:48 +08:00
xfy
df85df3d2e style(empty-state): 配图加圆角,暗色模式降低亮度
- rounded-lg 圆角(= 项目的 --radius-paper 8px)
- dark:brightness-90 滤镜,让彩色配图在暗色主题下退后一些
2026-06-25 13:48:46 +08:00
xfy
cfd2af1fab refactor(empty-state): 内联 SVG 换成 webp 配图
移除手绘的 LineDog 内联 SVG,改用 <img> 引用
public/images/xiaotiaoxiaogou_01.webp 原始插画。
2026-06-25 13:43:23 +08:00