763 Commits

Author SHA1 Message Date
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
824ad5a3d3 chore(Makefile): remove build hints and start target
Build instructions have been moved to AGENTS.md.
The verbose echo messages and the standalone start target
are no longer needed.
2026-06-29 09:59:14 +08:00
xfy
7615def3d1 fix: add required [web.resource.dev] for dioxus 0.7.9
dioxus-cli 0.7.9 requires a `dev` sub-table under [web.resource].
Added [web.resource.dev] with the same style/script arrays as the
production [web.resource] section.
2026-06-29 09:56:07 +08:00
xfy
18db852f30 fix(build): correct asset path resolution for local release build and remove duplicated resources 2026-06-26 18:35:48 +08:00
xfy
225c477f8c chore(theme): remove debug prints 2026-06-26 18:27:27 +08:00
xfy
82a8c511fc fix(theme): remove dark class from AppRouter div 2026-06-26 18:24:00 +08:00
xfy
230ec60f24 fix(theme): remove transition from body to fix VT snapshots
The CSS transition on the body's background-color (0.3s ease) was
causing the browser's View Transitions to capture a stale snapshot
during the Dark -> Light change. Even though the .dark class was
removed synchronously and we tried to disable transitions via an
!important override class, the interaction between Tailwind classes
and the raw CSS transition on body still caused the root VT snapshot
to render as Dark when it should have been Light.

By removing the transition from body entirely (which is redundant
anyway since we're using View Transitions for theme changes), the
body jumps to its target color instantly, guaranteeing the NEW
snapshot accurately reflects the final Light theme, eliminating
the 'ghost circle' or 'circle outline' bug.
2026-06-26 18:21:13 +08:00
xfy
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
xfy
93d3858a37 chore(deps): update package-lock.json 2026-06-26 18:15:00 +08:00
xfy
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
xfy
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
xfy
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
xfy
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
xfy
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
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
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
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
a0067c73d6 chore(core): yggdrasil-core 接入构建管线与全局注入
Dioxus.toml 追加 yggdrasil-core 的 css/script;Makefile 加
build-core / build-core-incremental,接入 build/build-linux/dev/test。
2026-06-26 13:58:54 +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
afabdf4655 chore(core): 搭建 yggdrasil-core 子工程骨架
照搬 lightbox 的 Vite IIFE 工程结构,作为核心 JS 统一归处。
此 commit 仅打通构建管线,业务逻辑后续 task 填入。
同时把 public/yggdrasil-core 加入根 .gitignore。
2026-06-26 13:51:39 +08:00
xfy
731547e6df docs(agents): 新增 Workflow 章节,约定每完成功能点即提交
Some checks failed
CI / check (push) Failing after 5m8s
CI / build (push) Has been skipped
- Agent 自主判断提交时机,验证通过后直接提交,无需等待指令
- 顺带更新 make build 注释,补充 doc 步骤与 make doc/doc-open 命令
2026-06-26 11:08:55 +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
2b5cd5f64f docs(makefile): cargo doc 默认使用 ayu 主题
通过 RUSTDOCFLAGS="--default-theme=ayu" 透传给 rustdoc,生成产物
根节点写入 data-theme="ayu"。默认值语义:浏览器未记住主题时生效。
2026-06-26 10:17:12 +08:00
xfy
aa3bbdc522 docs(pages): 补充 pages 模块文档
最后一个缺模块文档的文件,至此 cargo doc 三维度全满:
371 pub 项全覆盖 / 122 文件模块文档全覆盖 / 零 rustdoc 警告
2026-06-26 10:05:40 +08:00
xfy
31bd59ba25 chore(skills): 移除 frontend-design skill 2026-06-26 09:58:47 +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
015edc5b6a test(post_detail): 锁定 slug 重取不再依赖镜像 signal
防止 post_detail.rs 回退到 render 期 set signal 反模式(commit 225bb24 已修)。
用源码字符串契约断言,与 theme.rs 的 preload-script 测试同模式。
2026-06-26 09:34:17 +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
9e9a3faeed docs(plan): 新增 Dioxus 反模式清零实施计划
覆盖 antipatterns 文档点名的三类反模式的剩余项:
- Task 1: trash.rs 的 dirty 派生值改 use_memo(小)
- Task 2: 为已修复的 post_detail slug 反模式补回归测试(小)
- Task 3: 从 write_editor 抽取 CoverUploader 子组件(大)

god component 全量拆分(TrashPage 等)明确排除出本计划,列为后续独立计划。
Task 3 经核对真实源码(行号、API 调用、import 路径)写成「移动现有代码」式步骤。
2026-06-25 18:33:56 +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
xfy
97b413dd50 docs: 同步 AGENTS.md 与当前代码
修正与代码不符的过时描述:
- server functions 分布补全 comments/ 与 settings.rs(原仅列 auth + posts)
- rate_limit tiers 从 3 个更正为 5 个(strict/upload/image/comment/unknown)
- Rust 测试数 392 → 402

补全 lightbox 子项目文档(新增独立章节,说明其经 Dioxus.toml
全局注入而非 wasm-bindgen 桥接的差异),并在 Build Artifacts
与 make test 描述中补上 lightbox 产物与测试(23 tests)。
2026-06-25 13:39:28 +08:00
xfy
8b581649a6 feat(home): 用「线条小狗」插画替换首页空状态
原空状态仅有一行「暂无文章」文案。新增 EmptyState 组件:
- 内联单色 SVG 配图(双手持相机,取景器内两只小狗),由 webp 重绘为线条画
- 描边用 currentColor 适配明暗主题,屏幕区填 accent-soft 作焦点
- 标题改「还没有文章」,副文案呼应站点定位
- 保留可选 action 字段供后台上下文复用

颜色、圆角、入场动画均沿用项目 Forest 调色板与既有视觉语言。
2026-06-25 13:37:07 +08:00
xfy
4ec8d72b66 feat(db): 启动期自动创建目标数据库
把 scripts/migrate.sh 里 CREATE DATABASE 的逻辑内置进二进制启动流程,
全新部署不再需要手动建库即可首次启动。

- src/db/pool.rs: 新增 ensure_database(),连接 postgres 维护库做
  CREATE DATABASE IF NOT EXISTS 等价逻辑(SELECT EXISTS + CREATE)。
  复用 MIGRATE_STARTUP_TIMEOUT_SECS 窗口应对 DB 起得慢;目标库名经
  is_simple_ident 校验后才拼到 SQL,不合法的名字跳过让正常路径报错。
  tokio_postgres::Config 不 Clone,故逐字段拷贝到新 Config。
- src/main.rs: 在 get_conn_for_startup() 之前调用 ensure_database(),
  失败走统一的 tracing::error! + exit(1) 路径。
- src/db/migrate.rs: 文档注释补明目标库存在性由 ensure_database 上游保证。

新增 2 个 is_simple_ident 单测(392 → 402 tests)。
2026-06-25 11:46:50 +08:00