592 Commits

Author SHA1 Message Date
xfy
59edc7c4e4 feat(sql-console): 表头 sticky + 数据行截断 + 点击展开详情 2026-07-06 11:01:11 +08:00
xfy
b0833eebf7 feat(sql-console): 单元格类型化渲染 (NULL/布尔/数字/文本) 2026-07-06 10:33:46 +08:00
xfy
6d0dd8bb6e feat(sql-console): SqlResultTable 组件骨架 + 模块挂载 2026-07-06 10:30:30 +08:00
xfy
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
xfy
b207babdb6 refactor(admin): 统一按钮令牌,消除样式散落
将 7 个 admin 页面中复制粘贴/各自为政的按钮 class 收敛到 ui.rs 令牌:

主操作色系统一为主题绿(bg-paper-accent):
- dashboard/posts「发布文章」、write「发布/更新」+「确认」、runner 语言切换选中态
  改用 BTN_PRIMARY / BTN_PRIMARY_SM(原为深色 bg-paper-primary,现统一为绿)
- system 4 处刷新/执行/创建备份、trash 保存设置、write 发布按钮改用 LoadingButton
  统一 loading 态为 spinner 绝对居中叠加(按钮宽度不变,原 system 用文字切换、
  write 用整体变灰,现全部一致)

描边/图标/行内按钮统一:
- posts 重建、system 刷新列表 → BTN_OUTLINE
- trash 清空回收站 → BTN_DANGER_OUTLINE
- trash 步进 ± 按钮(2 处重复字符串)→ BTN_ICON
- write 关闭 × 按钮(2 处重复)→ BTN_CLOSE_ICON
- system BackupRow 恢复/删除绕过令牌 → BTN_TEXT_AMBER / BTN_TEXT_RED
  (删除色从 red-600 校正为令牌的 red-500,与其他行内删除一致)

消除约 15 处内联 class 字符串重复,system.rs 4 种主操作变体收敛为 1 种。
2026-07-06 00:17:25 +08:00
xfy
545651d304 refactor(ui): 新增 BTN_PRIMARY/BTN_OUTLINE 等按钮令牌与 LoadingButton 组件
消除 admin 页面按钮样式散落问题:
- BTN_PRIMARY / BTN_PRIMARY_SM:主题绿实心胶囊主操作按钮
- BTN_OUTLINE:描边次要按钮(relative 以承载 spinner 叠加)
- BTN_DANGER_OUTLINE:红色描边危险按钮(清空回收站)
- BTN_CLOSE_ICON / BTN_ICON:× 关闭与方形步进图标按钮
- LoadingButton 组件:封装主题绿三态(loading spinner 叠加 / disabled 灰化 / 正常),
  统一全站 loading 态为 posts.rs 的 spinner 叠加模式(按钮宽度不变,避免布局抖动)
2026-07-06 00:17:09 +08:00
xfy
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
xfy
e1b312e651 fix(admin): SQL 编辑器 gutter 与 content 背景割裂
现象:行号区和代码输入区背景色看起来不一致,有割裂感。

根因:三层背景色各不相同——容器用 paper-entry(mantle #e6e9ef)、
标题栏用 paper-theme(base #eff1f5)、CodeMirror 自带 base 背景。
容器与编辑器同处 base/mantle 之间,层次扁平,CodeMirror 的 base
浮不出层次,gutter 与 content 的细微差异被放大。

修复:建立 crust > mantle > base 三层明确递进——
- 容器改 paper-code-block(crust #dce0e8,最深)
- 标题栏改 paper-entry(mantle,中)
- CodeMirror 保持自带 base(最浅)

编辑器作为「浅色岛」浮在深色容器上(VS Code / Sublime 的 IDE
经典范式),gutter 与 content 同为 base 完全融合,割裂消失。
2026-07-05 23:10:56 +08:00
xfy
a7a97fc299 fix(code-runner): 去掉 nproc ulimit,修复容器启动 EAGAIN
容器启动时报 'exec /bin/sh: resource temporarily unavailable',根因是
HostConfig 里的 ulimit nproc=64 与 non-root user(1000:1000) 组合:
RLIMIT_NPROC 按 UID 计数且在容器初始 exec 阶段就生效,与容器内实际进程数无关,
导致 sh 都起不来。pids_limit=64 已在 cgroup 层兜底进程数限制,nproc 是冗余且
有害的双重约束。

复现:docker run --ulimit nproc=64 --user 1000:1000 → exec 失败
       去掉 nproc(保留 nofile + pids_limit + 其他沙箱约束)→ python/node 正常运行

cargo test infra::docker 全过。
2026-07-05 22:05:01 +08:00
xfy
2cfc64b319 fix(admin): SQL 控制台 Ctrl+Enter 触发 panic(无 dioxus scope)
现象:编辑器有焦点时点击执行(或按 Ctrl+Enter)会 panic:
  called `Option::unwrap()` on a `None` value  (runtime.rs:223)
  RefCell already borrowed                       (runtime.rs:280)

根因:Ctrl+Enter 从 CodeMirror 的 JS 事件处理中触发 onRunShortcut
回调,此时 dioxus scope stack 为空。execute_for_editor 内部调
spawn(),而 spawn() 走 Runtime::with_current_scope →
current_scope_id().unwrap(),scope stack 空时直接 panic。
第二个 panic 是 unwind 期间再次 borrow scope_stack 触发的。

修复:用 dioxus 文档推荐的 web-sys 回调模式——在组件主体捕获
scope_id(此时 scope 活跃),JS 回调里用
Runtime::current().in_scope(scope_id, ...) 重建 scope 上下文
后再执行,spawn 即可找到 origin scope。

dioxus-core 源码(global_context.rs docstring)明示此模式,
dioxus-signals/src/global/mod.rs:201 也是这样用的。

注意:按钮 onclick 走 dioxus 事件系统(scope 已设),不受影响;
仅 CodeMirror JS 回调路径需要 in_scope 兜底。
2026-07-05 21:59:17 +08:00
xfy
fe6934ddd5 feat(admin): SQL 控制台接通 Ctrl+Enter 并重做视觉
接通上一提交打通的快捷键通道,同时重做编辑器/工具条/结果区视觉。

功能(接通 Ctrl+Enter):
- 把 run_sql 逻辑拆出 execute_for_editor 闭包(仅捕获 Copy 的 Signal),
  注入 CodeMirror onRunShortcut,按钮 onclick 仍走 run_sql
- 两条路径共用同一套 4 道护栏 + 资源逻辑,行为完全等价

视觉(编辑器标题栏一体化):
- 编辑器加标题栏:左 accent 状态点 + SQL 标识,右 ⌘↵ 快捷键提示
- 容器升至 rounded-2xl + 280px 最小高度,留呼吸
- 执行按钮改胶囊式(rounded-full)+ active:scale 触觉反馈 +
  SPINNER_SVG 替代纯文字「执行中...」
- 危险选项(我了解后果)用竖线分隔 + 红色语义,与普通选项分层
- 结果摘要从裸文本改徽章式(accent-soft 底 + 圆角胶囊)
- 加空结果友好态:SELECT 返回空集时显示「查询成功,无返回行」
- EXPLAIN 输出 pre 补 m-0,消除默认外边距
2026-07-05 21:31:16 +08:00
xfy
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
xfy
1dc02561a3 style(code-runner): 重做 admin/runner 视觉,对齐项目 token 体系
页面与 CodeRunner 组件此前用的是 daisyUI 类(base-100/btn-primary/
input-bordered/bg-neutral 等),但项目根本没装 daisyUI——这些类在
编译产物里 0 匹配,导致页面渲染成裸结构,是「半成品」观感的根因。

全部换成项目真实的 --color-paper-* 语义 token,视觉与 dashboard /
posts / system 等后台页面统一:

页面壳(runner.rs):
- h1 升至 text-4xl,与其它 admin 页一致;加底部分割线
- 外层对齐 max-w-5xl space-y-8
- 语言切换改为胶囊按钮组(选中态 accent 绿实心)
- overrides 输入框套 system.rs 的表单范式,加 focus 态
- 错误/提示用 red-500 dark: red-400,解析成功时显示字段说明

CodeRunner 组件(runner.rs):
- 容器改 rounded-2xl + paper-border + paper-entry
- 顶栏加状态点(accent)+ 胶囊运行按钮(active 缩放反馈)
- 运行中用 SPINNER_SVG 替代 daisyUI loading-spinner
- 输出区改 paper-code-block 底 + 语义色文字,随主题切
- 错误区改 red-50 dark: red-900/10 柔光块
2026-07-05 14:49:24 +08:00
xfy
c83b834c8b feat(code-runner): admin 跳过代码运行速率限制
作者在 /admin/runner 沙箱调试代码块时,1 次/秒 + 50 次/天的读者限流
(RATE_LIMIT_CODE_EXEC_*)会频繁打断试运行。已登录 admin 改为跳过
check_code_exec_limit,仍受并发槽、资源钳制与源码大小校验约束。
2026-07-05 14:32:56 +08:00
xfy
575b6b533e feat(code-runner): CodeRunner 挂载 CodeMirror 编辑器,支持双向绑定
兑现组件一直承诺但无人实现的「调用方挂载编辑器」契约——此前容器 div 只
输出 id,admin/runner 沙箱与文章正文 runnable 块都只能提交写死的初始源码,
作者/读者无法编辑。

- CodeRunner 在 WASM 端按自身 container_id 调 codemirror_bridge.create
  挂载编辑器,onChange 回写内部 source_signal(编辑器内容 = 唯一真源)
- run_code 改读 source_signal 当前值,提交编辑后内容而非初始快照
- 主题切换经 use_resolved_theme 订阅,热切换编辑器主题
- use_drop 销毁 EditorHandle,释放 CodeMirror 实例与 closure 槽位
- source prop 外部变更(admin 切换语言重置示例)同步进 signal + 编辑器
- admin/runner 页面 overrides 解析挪进 use_memo,避免 render 体写状态

范式镜像 SQL 控制台(system.rs)与 Tiptap 编辑器(write.rs)。
2026-07-05 14:32:39 +08:00
xfy
58f1a981d3 feat(admin): SSR 层认证守卫,未登录直接跳登录页
此前后台鉴权完全在客户端 WASM 完成:SSR 渲染骨架屏 → 浏览器下载
/编译 WASM → hydrate → 异步 get_current_user() → 客户端 navigator.
push(Login)。整条链串行,未登录用户首屏要空白好久才跳转登录。

新增 admin_guard 中间件:/admin* 请求在进入 Dioxus SSR 渲染器之前
读 session cookie → get_user_by_token 校验 admin 角色 → 失败直接 302
到 /login。未登录用户首屏即登录页,零 SSR 渲染开销。

- 置于 app_routes layer 链最外层(CSRF/cache/SSR 之前短路)
- 复用 get_user_by_token:命中内存缓存 + session_generation 校验,
  与客户端鉴权同一套语义
- DB 错误 fail-open(避免抖动把已登录管理员踢登录页),客户端
  AdminLayout 仍作兜底
- /admin 与 /login 本就不进 SSR 缓存,302 不会被缓存
2026-07-04 22:53:28 +08:00
xfy
16bd644783 fix(admin): 仪表盘空数据时显示空状态占位
空库下 get_post_stats / list_posts 正常返回,但 list_posts 返回
Some([]) 时走有数据分支、循环 0 次,渲染出一个空表格容器,视觉
上仪表盘近期文章区域一片空白,没有空状态引导。

复用项目既有的 empty_state::EmptyState 约定(与 posts.rs / trash.rs /
comments.rs 列表页一致):Some(posts) 且 posts.is_empty() 时渲染
暂无文章插画 + 写文章CTA。空状态放在 ADMIN_TABLE_CLASS 容器
之外,避免 overflow-hidden 裁掉插画的 py-20 内边距。
2026-07-04 22:49:25 +08:00
xfy
383e6f6b43 fix(db): expand source chain for all DB error logging
The previous fix only covered migration errors. The same truncation
affected two more places, both because tokio_postgres::Error's Display
prints only "db error" while the real message lives in source():

- api/error.rs: AppError::{db_conn,query,tx} logged with {e}, so every
  runtime DB failure showed as "Query failed: db error". The
  constructors' bound is tightened from impl Display+Debug to
  impl std::error::Error so the source chain can be walked; the
  user-facing message stays sanitized (existing tests still pass).
- db/pool.rs: ensure_database formatted tokio_postgres::Error directly
  into the String returned to main.rs' exit(1) path, collapsing to
  "failed to query pg_database: db error" etc.

Adds a shared crate::db::format_with_sources helper (walks source()
with de-dup of placeholder layers) and reuses it from MigrateError's
Display, the AppError constructors, and pool.rs.
2026-07-04 21:54:54 +08:00
xfy
e9f0b032b7 fix(db): expand migration error display to walk source chain
tokio_postgres::Error's Display prints only the placeholder "db error"
for DB-side failures; the actual message (e.g. column already exists,
SQLSTATE, constraint name) lives in source() as DbError. Without
traversing the chain, startup logs showed the useless
"migration 001 failed: db error", making failures impossible to
diagnose.

MigrateError::Display now appends each source layer with ": " and
exposes source() so error-reporting layers that walk the chain
themselves also benefit.
2026-07-04 21:47:24 +08:00
xfy
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
xfy
bbd4553467 feat(admin): add dedicated code-runner sandbox page at /admin/runner
不改动 852 行的 write.rs 编辑器布局,改为独立 admin 页面(与 posts/system 同款):
- runner.rs: Runner 页面(语言切换 join 按钮 + overrides JSON 输入 + CodeRunner 沙箱)
  切换语言自动填充示例源码;overrides JSON 实时校验,畸形时提示并忽略
- mod.rs: 注册 runner 模块并导出 Runner
- router.rs: 新增 #[route("/runner")] Runner {} 嵌套在 /admin 下
- admin_layout.rs: 导航新增「试运行」入口
2026-07-03 18:41:05 +08:00
xfy
0e8b5fd7b7 feat(ui): scan and mount code-runner component client-side in reader
采用 Dioxus vdom 友好的片段拆分(避免篡改 dangerous_inner_html 产物导致 hydration 冲突):
- post_content.rs: split_content_fragments 把 content_html 拆成 Html/Runnable 片段,
  Runnable 作为 <CodeRunner> 组件穿插渲染;HTML 实体解码还原源码;6 个单测
- markdown.rs: 可运行 pre 增加 data-source(转义后的原始源码), 供阅读器无损提取
- sanitizer.rs: 放行 pre 的 data-source 属性

(任务 7 的 data-source 增量一并在此提交)
2026-07-03 18:35:57 +08:00
xfy
6b875e6ba5 feat(ui): create CodeRunner Dioxus UI component with async polling
- runner.rs: CodeRunner 组件(源码展示 + Run 按钮 + 阶段/输出/错误区)
  spawn 提交 StartExec 后用 sleep_ms(WASM 友好) 轮询 GetExecResult,MAX_POLLS 兜底
  mut 信号加 cfg_attr 放行 server 的 unused_mut(AGENTS.md 约定)
  提前克隆 source/language 给 move 闭包,避免 rsx! 二次借用 (E0382)
- execute.rs: 修正双目标可见性——server fn 模块不能 cfg-gate
  共享类型 use 双目标可见,server-only 辅助 use 单独 gate(对齐 posts 模块约定)
- mod.rs: execute 不再 gate,languages/progress 仍 gate
2026-07-03 18:28:52 +08:00
xfy
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
xfy
a77a2e3502 feat(markdown): support rendering and sanitizing runnable pre code-blocks
- markdown.rs: CodeBlock 处理识别 `runnable` 标记 + 受支持语言,在 <pre> 上
  挂 data-runnable/data-lang/data-overrides;overrides JSON 经 escape_html 转义
  防属性注入;非可运行围栏的 language-xxx 改取纯语言 token(首个空白前)
- sanitizer.rs: clean_html 放行 <pre> 的 data-runnable/data-lang/data-overrides
- 新增 4 个 markdown 测试 + 2 个 sanitizer 测试覆盖转义/白名单/字段顺序
2026-07-03 18:19:26 +08:00
xfy
9fe128d44d feat(api): implement StartExec and GetExecResult server functions
- rate_limit.rs: 新增 CODE_EXEC 双层限流(per-second burst + 每日总额)
  governor 0.8 无 per_day,用 with_period(24h)+allow_burst 模拟
- execute.rs: StartExec(IP 提取/语言白名单/源码大小/入队/并发信号量/clamp/容器执行)
  + GetExecResult(轮询读取 DashMap);系统异常脱敏,日志记详情
- .env.example: RATE_LIMIT_CODE_EXEC_PER_SEC 改为整数(governor 不支持小数)
2026-07-03 18:15:59 +08:00
xfy
3f3bbf284c feat(api): add language registry and markdown fence info parser 2026-07-03 18:11:43 +08:00
xfy
9baa9da62f feat(api): implement in-memory task registry with gc 2026-07-03 18:10:20 +08:00
xfy
60fd6378b6 feat(api): define shared serialized structs for code runner
Co-authored-by: plan docs/superpowers/plans/2026-07-03-code-runner.md Task 3
2026-07-03 18:09:27 +08:00
xfy
5fdc88a48c fix(runner): resolve task 2 container runner review findings 2026-07-03 18:02:56 +08:00
xfy
d8d30df80c feat(infra): implement Docker execution layer using bollard client 2026-07-03 17:59:52 +08:00
xfy
b0ebba3475 fix(runner_config): remove server gate on runner_config, safeguard clamp logic, and parse allow_network case-insensitively 2026-07-03 17:52:50 +08:00
xfy
60725053e4 feat(infra): implement runner config parsing and resource limits clamping 2026-07-03 17:50:42 +08:00
xfy
2fba43dc3f chore(infra): add bollard dependency and modular structure 2026-07-03 17:49:08 +08:00
xfy
46860f7c41 fix(ui): apply mount animation inside each page component to avoid skeleton interception 2026-07-03 17:24:38 +08:00
xfy
d361bca220 feat(ui): add smooth mount animation for route transitions 2026-07-03 17:21:04 +08:00
xfy
197c08d258 style(ui): fix inconsistent padding on post card 2026-07-03 17:13:02 +08:00
xfy
e18fe18cc3 fix(admin/layout): move scrollbar to outer card edge for all admin pages 2026-07-03 16:53:10 +08:00
xfy
52b4f91622 fix(admin/write): restore scrollability by using overflow-y-auto and min-h-full 2026-07-03 16:50:53 +08:00
xfy
acfddb3e17 style(admin/write): overhaul write page to modern minimalist layout 2026-07-03 16:44:39 +08:00
xfy
d3a50728ed style(admin): overhaul layout to modern minimalist sidebar design 2026-07-03 15:50:59 +08:00
xfy
38ba6692d3 style(admin): redesign admin UI with soft rounded corners and Catppuccin theme 2026-07-03 15:46:18 +08:00
xfy
9f69a132fa refactor(admin): redesign write page for industrial aesthetic 2026-07-03 15:37:41 +08:00
xfy
cad0e3d83b fix(admin): restrict write page width to match other pages 2026-07-03 15:35:06 +08:00
xfy
d1041a84dd feat(admin): complete redesign of backend ui with industrial minimalist aesthetic 2026-07-03 15:32:02 +08:00
xfy
45f4e9cde0 refactor(ui): 去除 Rust 中硬编码颜色,统一语义色阶
跟随 Catppuccin 配色迁移,清理 src/ 中绕过 paper-* 语义 token 的
硬编码 dark:[#hex] 任意值,改用 Tailwind 默认灰阶(gray-700/800),
它们随主题切换自然适配。

- post.rs 状态徽章:dark:bg-[#333]/dark:text-[#9b9c9d] → gray-800/gray-400/500
  (同步更新测试断言)
- comments/* 与 skeletons/* 的 dark:[#2a2a2a]/[#2e2e33]/[#333]/[#5a5a62]
  → 对应 gray-600/700/800
- trash.rs 状态灯发光圈 rgba 由旧鼠尾草绿(92,122,94) 改 Latte green(64,160,43)
- ui.rs BTN_SECONDARY 注释更新为新 Teal 强调色值

注:src/pages/admin/posts.rs 的 clippy redundant_closure 报错为既有问题,
与本次配色迁移无关,不在范围内。
2026-07-03 14:25:40 +08:00
xfy
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
xfy
eb3087edf4 fix(theme): 跟随系统切换用主题按钮位置作为 VT 圆心
诊断发现跟随系统与手动点击的 JS 路径完全一致(__startViewTransition 全程
正常,无降级、无 reject),「看起来像淡入淡出」实为圆形展开动画在屏幕中心
圆心下的必然视觉效果:从中心向四周等距扩散,各方向同步推进,近似整体覆盖,
与手动点击(按钮在右上角,圆弧明显横扫)观感差异大。

改用 .theme-toggle 按钮的 getBoundingClientRect() 中心作为 VT 圆心,与手动
点击的动画起点一致;按钮不在 DOM 时回退到视口中心。同步更新注释说明。
2026-07-03 13:26:40 +08:00
xfy
188fc464e2 fix(theme): System 模式下系统偏好变化时同步 dark class
原实现存在缺口:<html> 的 dark class 运行时写入点只有「首屏预加载脚本」与
「手动点击 ThemeToggle」两处,System 模式下系统颜色偏好自动变化时无人同步——
尽管 system_dark signal 与 resolved memo 都会更新,下游(CodeMirror 等)也
能跟随,但页面配色纹丝不动,与文件头注释承诺的「实时同步 .dark class」不符。

新增 use_effect 兜底:仅 System 模式下 resolved 实际翻转时,以视口中心为圆心
复用 __startThemeTransition 触发 VT 圆形展开动画。Light/Dark 显式模式由
ThemeToggle::onclick 全权负责,effect 不介入,避免重复触发;首次挂载通过
prev_resolved 信号记录基线,自然跳过(DOM 已由 ThemePreload 设好)。
2026-07-03 11:35:10 +08:00
xfy
c79c390482 fix(ui): ADMIN_TABLE_CLASS 去掉 overflow-hidden,修复 tooltip 被裁
overflow-hidden 用于圆角裁剪表格,但会硬裁掉行内按钮的 tooltip
(无论朝上朝下)。去掉后:表格行 hover 背景 (bg-paper-entry) 与
容器背景同色,圆角处无视觉差异,tooltip 可正常溢出表格边界。
全局生效,所有 admin 表格的 tooltip 不再被裁。
2026-07-03 11:15:48 +08:00