23f0cc6222
fix(ui): adjust search placeholder size and position
2026-06-29 14:10:21 +08:00
b366c00e74
fix(ui): enlarge and better center search placeholder image
2026-06-29 14:07:35 +08:00
b666472193
fix(ui): add margin between post count and list in tag detail page
2026-06-29 14:05:47 +08:00
3f7d112898
feat(pages): add empty placeholder image to search page
2026-06-29 13:55:08 +08:00
528a79fb83
feat(pages): add empty state to tags and tag detail pages
2026-06-29 13:50:05 +08:00
373498870a
style: apply cargo fmt to workspace
CI / check (push) Failing after 5m35s
CI / build (push) Has been skipped
2026-06-29 13:47:40 +08:00
5e1becf116
fix(admin): explicitly set pointer-events auto for trash settings panel to resolve click bug
2026-06-29 13:46:05 +08:00
ef8af77a08
style(admin): format trash.rs
2026-06-29 13:42:29 +08:00
fdaefae027
test: fix test_auto_purge_settings_has_transition_class false positive
2026-06-29 13:37:59 +08:00
768dbc9510
feat(admin): add smooth slide-down animation to trash auto-purge settings panel
2026-06-29 13:34:43 +08:00
a47a5a6ac9
feat(admin): update empty state for trash page
2026-06-29 13:26:45 +08:00
c69a632e75
fix(ui): trigger FilterTabs indicator animation on click
2026-06-29 13:24:52 +08:00
3daa09efc2
fix(ui): fix WASM compilation error in FilterTabs indicator animation
2026-06-29 13:22:22 +08:00
ec5f933191
feat(ui): add smooth sliding animation to FilterTabs active indicator
2026-06-29 13:20:39 +08:00
d91a56f8b2
refactor(admin): extract comments filter tabs into FilterTabs component
2026-06-29 13:15:50 +08:00
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
a1aef5bda9
fix(admin): add cursor-pointer to comment status filter buttons
2026-06-29 11:41:52 +08:00
dbe95f1173
feat(admin): use global EmptyState component in comments management page
2026-06-29 11:39:32 +08:00
72d21c6670
feat(admin): use global EmptyState component in posts management page
2026-06-29 11:38:50 +08:00
46e9a29f1e
fix(theme): 消除 ThemeToggle 在 wasm/server 构建的 unused_mut 警告
...
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
b923851284
docs(main): 注明 CompressionLayer 默认跳过 image/*
...
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
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
cb73a9b979
chore: fix cargo clippy warnings (unused import and false positive unused_mut)
2026-06-29 10:14:27 +08:00
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
82a8c511fc
fix(theme): remove dark class from AppRouter div
2026-06-26 18:24:00 +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
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
30a861f910
docs(agents): 新增 yggdrasil-core 子工程章节
...
与 lightbox/tiptap-editor 并列,记录用途、构建、注入方式与
主题展开动画的 View Transitions 实现。同步更新 make test 注释
与 Build Artifacts 列表。
2026-06-26 14:09:46 +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
25e01a109b
fix(write): 移除 cover_uploading 多余的 mut 绑定
...
本组件只读取(on_submit 拦截)与传递给 CoverUploader,写入全在子组件
内部。Signal 是 Copy 类型,.set() 不要求 mut 绑定,编译器的 unused_mut
警告是真阳性。
2026-06-26 11:07:31 +08:00
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
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
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
aa3bbdc522
docs(pages): 补充 pages 模块文档
...
最后一个缺模块文档的文件,至此 cargo doc 三维度全满:
371 pub 项全覆盖 / 122 文件模块文档全覆盖 / 零 rustdoc 警告
2026-06-26 10:05:40 +08:00
f342742820
docs(write): 清理 CoverUploader 重复/失实的文档注释
...
最终 review 指出 c2eb261 留下了一段重复且失实的 doc(仍声称 cover_uploading
对本组件私有,与已改为双向绑定 prop 的事实矛盾)。删除旧段落,保留正确描述
两个双向绑定 signal 的版本。
2026-06-26 09:57:15 +08:00
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
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
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
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
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
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
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
c836e3e1df
feat(api): 新增 healthz/readyz 健康检查端点
...
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
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
7611c60e6c
feat(write): 保存按钮改用鼠尾草绿主色
...
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
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
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
df85df3d2e
style(empty-state): 配图加圆角,暗色模式降低亮度
...
- rounded-lg 圆角(= 项目的 --radius-paper 8px)
- dark:brightness-90 滤镜,让彩色配图在暗色主题下退后一些
2026-06-25 13:48:46 +08:00
cfd2af1fab
refactor(empty-state): 内联 SVG 换成 webp 配图
...
移除手绘的 LineDog 内联 SVG,改用 <img> 引用
public/images/xiaotiaoxiaogou_01.webp 原始插画。
2026-06-25 13:43:23 +08:00