|
|
1b772f4534
|
feat(mcp): 客户端配置代码块添加语法高亮
将配置片段 DTO 从扁平字段重构为 snippets Vec(含 content/content_html),
高亮在服务端经 highlight_code 完成(JSON 配置用 json 语法,CLI 用 bash),
前端以 dangerous_inner_html 渲染并包 .md-content 匹配 highlight.css 作用域。
补 json/bash 语法解析测试,确认两者均产出彩色 span 而非纯文本回退。
|
2026-07-28 15:53:20 +08:00 |
|
|
|
53376a0373
|
fix(mcp): 修正 Claude Code / Cursor 客户端配置格式(联网核实 2026)
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
依据 2026 官方文档联网核实所有 MCP 客户端配置格式,发现两处错误:
1. Claude Code .mcp.json:type 值应为 "http",不是 "streamable-http"。
Claude Code 用 streamable-http 会静默失败/卡在 connecting 状态。
2. Cursor ~/.cursor/mcp.json:不应带 type 字段——Cursor 按远程 URL 自动
识别 streamable-http,仅需 url + headers。此前 cursor_json 直接
clone claude_code_json,继承了错误的 type 字段。
其余配置核实无误:Cline(streamableHttp 驼峰 + disabled/autoApprove)、
Claude Code CLI(--transport http --header)、Oh-My-Pi(transport 字段)。
改动:
- 拆分 cursor_json,不再 clone claude_code_json
- 更新对应 doc 注释(config.rs / mcp_tokens.rs DTO)
- claude_code 测试断言改为 type=="http"
- cursor_json_equals_claude_code_json → cursor_json_has_no_type_field
|
2026-07-28 15:10:01 +08:00 |
|
|
|
78fb51a310
|
feat(mcp): 客户端配置新增 Oh-My-Pi 片段
在 /admin/mcp 客户端配置展示区增加 Oh-My-Pi(omp)的 MCP 配置片段,
位于 Cline 与「通用」之间。
Oh-My-Pi 的 mcpServers 条目与 Claude Code/Cursor 的关键差异:字段名
是 transport(而非 type),值仍为 streamable-http,auth 同样走
headers.Authorization。配置文件落在 ~/.pi/agent/mcp.json(全局)或
.pi/mcp.json(项目级)。
改动:
- ClientConfigs / McpClientConfigs 新增 omp_json 字段
- generate_client_configs 生成 omp 片段
- ConfigCard 增加 ConfigSnippet 行
- 新增测试 omp_json_uses_transport_field_not_type,并纳入 pretty 校验循环
格式依据:2026 pi.dev / oh-my-pi 官方文档(联网核实)。
|
2026-07-28 15:04:33 +08:00 |
|
|
|
063ad6e606
|
fix(mcp): 修复 ConfigCard use_effect 死循环
use_effect 内读 loading() 作为重入 guard,使 loading 成为响应式依赖:
effect 设 loading=true → loading 变化触发 effect 重跑 → 早退;
fetch 完成设 loading=false → loading 变化再次触发 effect → 再次跑,
无限循环,导致 /api/get_mcp_client_configs 被疯狂调用。
use_effect 本身按依赖去重,config_token 不变就不会重跑,guard 多余;
删掉 effect 内的 loading() 读取,loading 不再是依赖,循环解除。
(on_manual_input 内的 loading 读取在事件闭包里,不构成响应式订阅,保留。)
|
2026-07-28 14:57:40 +08:00 |
|
|
|
1d1b32cdd2
|
fix(admin): FormSelect 面板水平居中于窄触发器,修复双倍位移
紧凑触发器(如「手动」73px)下面板仅左对齐,宽出部分全堆在右侧,
视觉失衡。面板改为 left-1/2 + translateX(-50%) 以触发器中心居中,
宽出部分两侧对称探出。
两个 CSS 陷阱及其解法:
- select-enter 关键帧的 transform 会覆盖静态 transform,fill 结束时
位置跳变 → translateX(-50%) 烘进关键帧首尾(同 popover-enter 先例)
- Tailwind v4 的 -translate-x-1/2 utility 走独立 translate 属性,与
关键帧的 transform 属性叠加成双倍位移 → 面板改用任意属性类
[transform:translateX(-50%)]
浏览器实测:紧凑面板中心偏差 -0.13px、胶囊面板 0.00px(动画播完后测量)。
|
2026-07-28 14:53:59 +08:00 |
|
|
|
60360f6b49
|
fix(mcp): reveal/revoke 令牌时将字符串 id 解析为 Uuid
reveal_mcp_token / revoke_mcp_token 把前端传来的 String id 直接绑到
$1::uuid,tokio-postgres 解析参数类型为 uuid 后拒绝 String,报
'cannot convert between the Rust type String and the Postgres type uuid'
导致 /api/reveal_mcp_token 500。
参照 assets/delete.rs 的做法,绑定前用 uuid::Uuid::parse_str 转换;
解析失败按令牌不存在处理(reveal 返回 None、revoke 静默无操作),
与函数既有的'避免探测'语义一致。
|
2026-07-28 14:42:14 +08:00 |
|
|
|
e6f7704367
|
feat(admin): 全项目原生 <select> 统一替换为 FormSelect
将剩余 4 处原生下拉(write 编辑器状态胶囊、db_status/server_status
自动刷新、export 导出格式)全部迁移到主题化的 FormSelect:
- FormSelect 新增 trigger_class 可选 prop(缺省全宽表单款),并导出
FORM_SELECT_COMPACT_CLASS 供工具栏内联小下拉复用;write.rs 状态选择器
以胶囊 trigger_class 复刻原外观(面板贴底栏自动上翻,替代原
「隐形 select 覆盖 span」hack)
- 面板宽度改为 max(触发器, 最长选项)(w-max min-w-full + 100vw 上限),
紧凑触发器(如「手动」)下长选项(如「500ms」「SQL (INSERT)」)
不再被截断
- db_status/server_status 的字符串 match 反查收编为类型安全选项表
(Option<u32> 直进直出)
- 因 system/write 页面 SSR 真实引用 FormSelect,移除 FORM_SELECT_ID /
wrap_index 上不再需要的 #[allow(dead_code)]
src 下已无任何原生 <select> 元素。浏览器实测:紧凑面板 93.8px 宽于
触发器 73.8px、胶囊面板上翻贴顶 6px 间隙、选中 check 与高亮行正确。
|
2026-07-28 14:38:12 +08:00 |
|
|
|
717d17d5a7
|
feat(admin): FormSelect 自定义主题化下拉弹层,替代原生 select 弹窗
原生 <select> 的弹出列表由 OS/浏览器渲染,暗色主题下是白底系统菜单,
无法跟随 Catppuccin 主题。将 FormSelect 内部重写为
button[aria-haspopup=listbox] + 绝对定位面板(组件签名不变,调用方零改动):
- 面板/选项全部使用语义色 token,与 POPOVER_PANEL_CLASS 同源
(卡片化圆角 + 阴影),新增 select-enter 入场动画(150ms,
含 prefers-reduced-motion 豁免)
- 键盘:focus 留在触发器,aria-activedescendant 高亮;↑↓ 循环
(wrap_index)、Enter/Space 选中、Esc 关闭、Home/End 跳首尾、
Tab 关闭并自然流转焦点;选项 onmousedown 阻止默认行为避免抢夺焦点
- 透明遮罩拦截外部点击关闭(同 Popover 模式)
- 打开时按视口空间决定展开方向(should_flip + measure_flip,
下翻/上翻分别设 transform-origin)
- 打开/键盘导航时高亮项 scrollIntoView(block: nearest)
- 选中行:text-paper-accent + check 图标;高亮行:accent-soft 背景
should_flip / wrap_index 抽为纯函数并配单测(边界:恰好放得下、
上方更窄不翻、空列表回绕安全)。
|
2026-07-28 14:19:52 +08:00 |
|
|
|
8b4a2b674e
|
feat(admin): 封装 FormSelect 组件并修复 MCP 页下拉箭头错位
/admin/mcp 的两个 <select> 直接套用文本框的 INPUT_CLASS,原生下拉箭头
由浏览器绘制,深色主题下位置/颜色不协调(垂直不居中、无法跟随主题色)。
- components/forms.rs 新增泛型 FormSelect:appearance-none 隐藏原生箭头,
右侧叠 pointer-events-none 的 chevron SVG(top-1/2 -translate-y-1/2
精确居中);option value 内部用序号占位,onchange 直接回传选中项的 T,
调用方免去「字符串反查枚举」样板
- mcp.rs 两个下拉改用以简形式传入 SCOPE_OPTIONS / LIFETIME_OPTIONS
浏览器实测:chevron 垂直居中偏差 0.00px,右间距 16px(与左内边距对称)。
|
2026-07-28 13:52:28 +08:00 |
|
|
|
ef4c4ee8ed
|
docs(mcp): CHANGELOG + .env.example + AGENTS.md + DEPLOYMENT(T7)
- CHANGELOG:[Unreleased]/Added 记录 MCP 服务器功能(传输/认证/作用域/工具面/加固/配置/依赖/环境变量)。
- .env.example:新增 MCP_TOKEN_ENC_KEY(必填,openssl rand -hex 32)+ RATE_LIMIT_MCP_PER_SEC/_BURST。
- AGENTS.md:新增「MCP Server」章节(传输/认证/令牌存储/作用域/工具组合模式/feature 门控/配置生成)+ Key Directories 加 src/mcp/ + 迁移数 14→17。
- DEPLOYMENT.md:必设变量表加 MCP_TOKEN_ENC_KEY;新增「/mcp 端点」反代说明(catch-all 代理/Origin 白名单取自 APP_BASE_URL/bearer 不经 cookie/独立限流桶)。
实时客户端冒烟测试延后到用户环境(需运行中的 DB + MCP_TOKEN_ENC_KEY + 真实 MCP 客户端);
机制已由 T1 探针验证,集成路径在工单 T7 记录了用户侧步骤。
|
2026-07-28 12:01:23 +08:00 |
|
|
|
45356c3766
|
feat(mcp): 端点加固 — token 限流、last_used_at 节流、审计日志(T6)
- token-keyed 限流:MCP_LIMITER 按 token_id 计数(与 web 的 IP-keyed 限流隔离),
默认 10/s burst 30,经 RATE_LIMIT_MCP_PER_SEC/_BURST 可调,超限返回 429。
- last_used_at 节流:仅在 NULL 或距上次 ≥ 60s 时写库,避免高频请求每次 UPDATE。
resolve_principal 查询带出 last_used_at 做窗口判断。
- 审计日志:鉴权中间件记录每次已认证 MCP 请求(user_id/scope/token_id),
为自主 AI 客户端的活动留下事后追溯痕迹。
- 输出清理:经设计保证——JSON 序列化转义控制字符,搜索结果硬上限 50,
rmcp 默认 4MiB 请求体上限兜底。间接提示注入靠作用域隔离 + 客户端人工审批。
验证:clippy --all-features -D warnings 干净;659 单测 + 1 集成全绿;web/server 双目标编译通过。
|
2026-07-28 11:57:00 +08:00 |
|
|
|
9eac025b3e
|
feat(mcp): 完整工具面 + token 管理 + 管理页(T2-T5 并行实现 + 合并)
T2 token 管理(src/api/mcp_tokens.rs):create/list/reveal/revoke + get_mcp_client_configs
服务端函数,均经 get_current_admin_user 鉴权;TokenLifetime 枚举(1/7/30/90天/永不过期)。
src/mcp/config.rs 生成 4 种客户端配置(Claude Code/Cursor/Cline/通用 + CLI)。
src/pages/admin/mcp.rs 令牌管理页:列表 + 新建表单 + 一次性明文展示/重查 + 撤销 +
配置片段卡片。路由 /admin/mcp,导航项加入 admin_layout。
T3 read 工具 + Resources(tools/read.rs, resources.rs):search_posts/get_post/list_tags
(read 作用域,仅已发布);published-post Resources(post://{slug},游标分页)。
search_published 提取为共享函数。
T4 write 工具(tools/{posts,comments,tags,media}.rs):create/update/publish/trash/delete_post、
评论审核、标签 CRUD、媒体 base64 上传(WebP 转码 + 去重)。复用既有 helper 的 SQL 与
缓存失效(moka + ssr_cache)。write 作用域,可读草稿。
T5 admin 工具(tools/{settings,runner}.rs):get/update_settings、run_code(沙箱执行)。
admin 作用域。
合并(server.rs):用 rmcp tool_router 的「命名路由 + 组合」模式,7 个工具组在
YggMcpServer 上 impl,combined_router 用 + 合并成单一 ServerHandler。各工具组鉴权
独立,经 Extension<Parts> 读 McpPrincipal + scope.grants 校验。
新增依赖 base64 0.22(媒体上传解码)。验证:server/web 双目标编译通过、clippy
--all-features -D warnings 干净、659 单测 + 1 集成全绿(+20 新测试)。
|
2026-07-28 11:53:32 +08:00 |
|
|
|
7b9dfa7851
|
feat(mcp): tracer bullet — /mcp 端点骨架与 search_posts 工具
T1 探针式实现,接通端到端链路并验证双目标编译:
- 依赖:rmcp=3.0.0-beta.3(server/macros/transport-streamable-http-server/
transport-worker)+ aes-gcm,均经 server feature 门控。
- 迁移 017_mcp_tokens:UUID 主键、scope 约束、token_enc 密文 + token_hash
唯一索引(仅未撤销),users.id 外键级联。
- 模型 src/models/mcp_token.rs:TokenScope(read<write<admin 偏序,grants)+
McpToken/McpTokenSummary/CreateTokenResponse DTO。
- src/mcp/(server-only):
- crypto.rs:AES-GCM-256 加解密(hex 密钥、每次独立 nonce、8 项单测)。
- auth.rs:bearer→SHA-256→DB 常量查找→注入 McpPrincipal,刷新 last_used_at。
- server.rs:rmcp ServerHandler,search_posts 工具(read 作用域,pg_trgm 查询)。
- router.rs:StreamableHttpService(无状态+JSON 响应+Origin 白名单)挂载 /mcp。
- main.rs:merge mcp_route 到应用路由;models/mod.rs 注册 mcp_token 模块。
验证:rmcp+axum-0.8 兼容性经临时探针实测(tools/list、tools/call、Origin→403、
bearer→工具 principal 全链路);--features web 与 --features server 均编译通过;
clippy --all-features -D warnings 干净;639 单测+1 集成 +13 新 MCP 测试全绿。
|
2026-07-28 11:09:55 +08:00 |
|
|
|
801cb79e0d
|
docs(mcp): 新增 MCP 服务器调研与规格
调研(docs/mcp-research.md):对照 2025-11-25 spec 与 rmcp 源码,确认
Streamable HTTP 是 2026 唯一远程传输、stateless 为默认、bearer token 为
单租户务实方案、官方 rmcp 的 Tower StreamableHttpService 可挂载于 axum。
规格(docs/mcp-spec.md):锁定 9 项设计决策(受众/作用域/有效期/检索机制/
工具面/草稿可见性/加密存储/配置目标/构建路径)、模块划分、迁移、验收标准。
rmcp 锁定 =3.0.0-beta.3(稳定版 0.2.1 缺 Origin 校验等 spec 强制项)。
|
2026-07-28 11:09:45 +08:00 |
|
|
|
f6e1dff600
|
perf(admin): 放慢仪表盘动画节奏
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
- 卡片入场 400→600ms(内联 animation-duration 覆盖),阶梯延迟 0/60/120/180→0/120/240/360ms
- 近期文章行 row-enter 300→450ms,stagger 步进 50→80ms
- CountUp 滚动时长 500→900ms
|
2026-07-27 14:56:00 +08:00 |
|
|
|
61634882c5
|
feat(admin): 仪表盘添加分块进场动画与数字滚动
- StatCard 三张卡 animate-page-enter 阶梯入场(0/60/120ms),待审评论卡数据就绪后补挂入场类(180ms),近期文章行 animate-row-enter 逐行 stagger(50ms 步进)——动画挂在数据就绪的真实分支上,避免骨架屏截断
- 新增 CountUp 组件:数值 easeOutQuint 滚动(500ms),use_effect 内驱动保持渲染纯净;命中 prefers-reduced-motion 直接显示终值
- input.css 新增 row-enter 关键帧;reduced-motion 媒体查询补全 animate-page-enter/popover-enter/row-enter 覆盖(此前仅 page-enter 生效)
- BTN_SECONDARY 补 active:scale-[0.98] 按压反馈,与 BTN_PRIMARY 对齐
|
2026-07-27 14:39:10 +08:00 |
|
|
|
5743c7a581
|
feat(upload): 上传图片按内容 SHA-256 去重,重复上传复用已登记素材
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
assets 新增 content_hash 列(唯一索引,迁移 016;存量行保持 NULL
不参与去重)。上传在安全性校验后、转码前对原始字节算 SHA-256:
命中即刷新 created_at/updated_at(重启 7 天清理保护窗)并返回
已有 URL,跳过 GIF/WebP 验证、转码与落盘。并发同内容上传由唯一
索引 + INSERT ... ON CONFLICT DO NOTHING 兜底,落败者删落盘文件
复用胜出者路径。命中响应带 reused: true。仅精确去重,视觉相似
检测(pHash)有意不做。
|
2026-07-27 13:25:26 +08:00 |
|
|
|
775c58a9e6
|
feat(admin): 素材页支持多选与批量删除
卡片勾选框:仅未引用素材可选(与单删保护语义一致),hover 显现、
有选择时常显,选中卡片加主题色 ring;stop_propagation 防触发灯箱。
批量条:已选计数 / 全选本页(可切换取消本页) / 清除 / 删除所选
(两步确认),选择跨翻页与筛选保留。
服务端新增 BatchDeleteAssets:一次查询取引用存在性,被引用的跳过,
其余删文件(NotFound 容忍)+ 批量删 DB 行 + 清派生缓存,返回
删除/跳过/释放/失败统计。
|
2026-07-27 11:40:02 +08:00 |
|
|
|
248060d920
|
feat(admin): 素材页搜索框添加 300ms 防抖
query 仍作输入框受控绑定,debounced_query 才是请求参数:停顿
300ms 无新输入才提交,击键重启 effect 使旧延时任务经 peek 比对
后静默过期。页码重置移入防抖提交点——放在 oninput 里会让 page
变化用旧关键词抢发一次请求。
|
2026-07-27 11:29:28 +08:00 |
|
|
|
0086f0f3ea
|
feat(admin): 素材卡片「复制」更名「复制路径」
按钮复制的是 /uploads/ 相对路径而非完整 URL,名称如实反映行为;
title 同步为「复制图片相对路径」。
|
2026-07-27 11:23:57 +08:00 |
|
|
|
d610fd5837
|
feat(admin): 素材页「孤儿」更名为「未引用」
与「引用中」对仗更自然。覆盖统计行、筛选 tab、清理按钮、
清理结果横幅、卡片徽章及 purge 服务端消息;代码标识符
(orphan_count / purge_orphan_assets 等)保持不变。
|
2026-07-27 11:16:59 +08:00 |
|
|
|
a890257009
|
feat(admin): Pagination 组件支持页码跳转
当前页码渲染为可编辑输入框(仅回调式翻页且总页数 > 1 时):
聚焦编辑、回车跳转(自动夹取到合法范围,非法输入还原)、失焦回显。
posts / assets / trash 三个 signal 翻页页已接入 on_jump;
路由式翻页(comments、前台)不传 on_jump,页码保持纯文本。
|
2026-07-27 11:13:26 +08:00 |
|
|
|
e33e89c258
|
fix(assets): 工具栏右侧按钮沉入 FilterTabs 的 mb-6 区域紧贴横幅
工具栏 items-end 对齐的是 flex 项的 margin box:FilterTabs 带 mb-6 使容器
底端低于下划线 24px,右侧按钮组无底 margin 被一并拉到底端,距横幅仅 4px。
按钮组同加 mb-6(移除补偿用的 pb-1)后,按钮底边与 tabs 下划线齐平,
下划线→横幅、按钮→横幅、横幅→网格均为 24px。
|
2026-07-27 10:01:56 +08:00 |
|
|
|
b4bea7db0d
|
fix(assets): 操作横幅上下间距不对称(40px/8px → 24px/24px)
FilterTabs 自带 mb-6 在 flex 工具栏内撑出 24px,再叠横幅 mt-4 形成 40px
空洞,而横幅下方仅网格 mt-2 的 8px。横幅改 mt-4 为 mb-6:上方回归 tabs
标准间距 24px,下方与网格 mt-2 margin 塌陷同为 24px,与 comments/system
页节奏一致。
|
2026-07-27 09:57:15 +08:00 |
|
|
|
55e4712f28
|
fix(assets): 删除返回「素材不存在」时刷新网格自愈过期数据
行已被其他会话/重建删除时,本页网格仍显示幽灵卡片(success:false 不触发
reload)。refs 为空的业务拒绝即「行已不在 DB」,此时同样 bump reload,
让网格收敛到服务端真实状态;引用中拒绝(refs 非空)不误刷新。
|
2026-07-27 09:46:46 +08:00 |
|
|
|
b98059edcc
|
fix(assets): 引用徽标加 z-10 修复被 blur-img 展示层遮盖
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
灯箱改造(051ca94)把缩略图换成 .blur-img 双层结构后,.blur-img-full
自带 z-index:1,而徽标 span 是 z-index:auto,绘制序被图片压过,
导致「孤儿 / 被 N 篇引用」徽标在 DOM 中存在但不可见。
浏览器实测:修复前 elementsFromPoint 命中 blur-img-full,
修复后命中徽标本身,琥珀色孤儿药丸恢复显示。
|
2026-07-24 18:31:45 +08:00 |
|
|
|
99d33b54d2
|
fix(lightbox): 图集切换不重算几何导致图片压扁
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
gotoIndex 换图时只替换 src,target/baseW/baseH/transform 全部沿用
第一张图的几何,宽高比不同的图被套用错误的 scale(显示宽高比 =
自身宽高比 × 首图宽高比)。同时 .lightbox-img 的 object-fit: contain
与 transformFor 依赖的「位图填满布局盒」前提冲突,素材网格的方形
缩略图容器下首图即被压扁。
修复:
- swap() 在新图加载后按 naturalWidth/Height 重算 fitCentered 目标,
布局盒设为 target 尺寸、transform 归位 scale(1,1),baseW/H 同步
供关闭飞回动画复用
- 先换 src 再判 complete,避免用旧图的 complete 误判缓存命中
- 移除 .lightbox-img 的 object-fit: contain,恢复位图填满布局盒
新增回归测试:切换后几何必须按新图宽高比重算(红绿验证通过)
|
2026-07-24 18:15:11 +08:00 |
|
|
|
051ca947e3
|
feat(assets): 素材网格接入灯箱预览
- 缩略图改为与前台正文图一致的 .blur-img 双层结构(?w=20 占位 +
?thumb=300x300 展示层 data-src),点击由全局注入的 lightbox.js 接管;
图集模式(当前页内 ←/→ 切换),灯箱加载原图(data-src 去 query)。
- 数据异步到达/筛选/翻页/刷新后由 use_effect 调 __initLightbox 重新绑定
(双保险契约同 PostContent:__lightboxSelectors + 直接调用兜底)。
- lightbox.ts 补 data-lb-bound 幂等守卫:Dioxus keyed diff 复用节点时
重复初始化会叠加 click 监听(点一次开两次);附回归测试。
- invoke_optional_global 提取为 src/utils/js.rs 共享,post_content 复用。
浏览器实测:47 张绑定齐全,原图打开/图集切换/Esc 关闭正常,
筛选重建与重建索引重取后重绑幂等(单次点击仅 append 一次 overlay)。
|
2026-07-24 17:39:34 +08:00 |
|
|
|
fa40f4bd95
|
fix(assets): uuid 参数序列化失败导致上传 500
tokio-postgres 将 $1::uuid 的参数推断为 uuid 类型,String 无法
序列化(缺类型桥接),INSERT assets 报 error serializing parameter 0,
补偿删文件后返回 500。list/delete/purge/rebuild 的 $1::uuid 与
ANY($1::uuid[]) 全是同一颗未引爆的雷。
正路:tokio-postgres 启用 with-uuid-1,SQL 侧去掉全部 ::uuid cast,
Rust 侧全程 Uuid 类型,仅在 serde DTO 边界(WASM 共享模型)转 String;
外部传入的 id 在边界 parse_str,非法 id 走业务错误而非 500。
|
2026-07-24 17:04:35 +08:00 |
|
|
|
8f75a64bfd
|
fix(assets): SUM(size_bytes) 显式转 bigint 修复列表 500
Postgres 的 SUM(bigint) 返回 NUMERIC,tokio-postgres 反序列化为
i64 时类型不匹配,row.get(3) 直接 panic(release 的 panic=abort
下会崩掉整个进程)。COALESCE(..., 0)::bigint 修正。
|
2026-07-24 16:50:30 +08:00 |
|
|
|
45d70e1dc6
|
refactor(assets): 清理 lint,对齐项目惯例
- api/assets/mod.rs 按 posts/comments 惯例加
#![allow(clippy::unused_unit, deprecated, unused_imports)]
(#[server] 宏固有噪音)
- 页面交互导入按 target_arch = wasm32 门控;组件级放行非 WASM 未用绑定
- 合并 check_upload_dimensions 进 upload_dimensions(单函数返回尺寸,
消除死代码),测试同步更名
|
2026-07-24 16:44:47 +08:00 |
|
|
|
5612f16425
|
feat(assets): 封面「从素材库选择」联动
- AssetPickerModal:素材网格(最新 60 + 搜索)单击回填封面 URL,
内嵌「上传新图」(复用 upload_image_file,成功即选中,
失败留在 modal 内提示),上传期间置位 cover_uploading 拦截保存
- CoverUploader 空态加「素材库」入口(与「输入链接」并列)
|
2026-07-24 16:32:43 +08:00 |
|
|
|
0c47e87d30
|
feat(assets): 全量重建索引
- rebuild_assets_index:扫 uploads/(跳过点目录)→ upsert assets
(xmax=0 判别新增;技术字段 IS DISTINCT FROM 才更新,保留 alt,
幂等重跑 updated=0)→ 删文件已消失的行 → 全表扫 posts 重建 refs
- 磁盘扫描与 header 尺寸读取移入 spawn_blocking
- extract_asset_paths 提升 pub(crate) 供重建复用同一提取逻辑
- 前端「重建索引」按钮(进行中禁点,结果进横幅)
|
2026-07-24 16:27:02 +08:00 |
|
|
|
799455da43
|
feat(assets): 删除保护、孤儿清理与 alt 编辑
- delete_asset:引用中业务拒绝(Ok(success:false) + 引用文章列表),
孤儿硬删除(文件 + DB 行级联 refs + 派生缓存)
- purge_orphan_assets:一键清理无引用且超过 7 天保护窗的素材,
容忍单项文件删除失败,返回数量/释放字节/失败数
- update_asset_alt:管理性 alt 编辑
- invalidate_asset_caches:IMAGE_CACHE 前缀批量失效 + 尺寸缓存失效;
磁盘派生缓存(哈希命名不可枚举)由既有清理任务回收
- 前端:卡片操作区三态(复制/alt/删除 ↔ alt 编辑 ↔ 确认删除),
顶栏「清理孤儿」两步确认(带数量与总大小),操作结果横幅
|
2026-07-24 16:23:00 +08:00 |
|
|
|
99b96e7a0c
|
feat(assets): 素材管理只读列表页
- list_assets:引用状态筛选 + filename/alt 搜索 + 时间/大小排序 +
分页(60/页),一次响应带齐 tabs 计数与可清理孤儿统计
- /admin/assets 网格页:?thumb=300x300 缩略图卡片 + 引用徽标,
单路由客户端分页(对齐管理文章模式),导航加「素材」入口
|
2026-07-24 16:15:19 +08:00 |
|
|
|
0c811efd4d
|
feat(assets): 素材注册表数据层
- migration 015:assets 元数据注册表 + asset_refs 引用关联表
- upload_image 落盘后登记 assets(失败补偿删文件);尺寸校验复用
upload_dimensions 一次解析同时返回 (w,h)
- sync_asset_refs 镜像 sync_tags:文章 create/update 事务内
解析 content_html + cover_image 的 /uploads/ 路径重建引用
- Asset/AssetDto/AssetFilter/AssetSort 共享模型,id 以 String 承载
(SQL 侧 ::uuid cast,避免 uuid crate 进 WASM 构建)
|
2026-07-24 16:07:07 +08:00 |
|
|
|
adde92e15c
|
docs(assets): 素材管理功能设计规格
grilling 访谈确认的结论落盘:assets/asset_refs 双表、
sync_tags 模式引用同步、引用中禁删 + 孤儿 7 天窗一键清理、
手动重建索引回填、封面联动(编辑器正文选择器不做)。
|
2026-07-24 15:26:56 +08:00 |
|
|
|
de4bfafb72
|
chore: release v0.6.2
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
v0.6.2
|
2026-07-24 14:02:10 +08:00 |
|
|
|
2b048b94a3
|
fix(utils): sleep_ms 的 cfg guard 对齐 feature 模型,修复 web-only 构建失败
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
sleep_ms 原用 #[cfg(not(target_arch = "wasm32"))] guard tokio::time::sleep,
但 tokio 是 server-only 的 optional 依赖。此 guard 会在「非 wasm32 主机 + 仅
web feature」组合(如 cargo build --no-default-features --features web)下
误激活,此时 tokio 未引入,导致编译失败。
该 bug 长期被 [dev-dependencies] tokio 掩盖——cargo test / 开发构建总能拿到
tokio,只有发布/生产构建(排除 dev-deps)才暴露。符合 AGENTS.md 第 1 条
dual-target gating 标准:触及 server-only 依赖的代码必须用 #[cfg(feature = "server")]
而非 target_arch 区分。
修复:tokio 版本改为 #[cfg(all(feature = "server", not(target_arch = "wasm32")))],
并为无效构建组合「非 wasm32 且非 server」补一个 panic stub(该组合不是有效部署
目标,web feature 真实构建目标即 wasm32;stub 仅保证符号可编译,运行时不会触发)。
调用点 runner.rs 的 server 占位分支(通过 sleep_ms 继承同一问题)无需改动——
它只在 server target 下编译运行,sleep_ms 修复后其调用自然可编译。
|
2026-07-24 10:52:51 +08:00 |
|
|
|
5a10b07bff
|
fix(router): 修复错误冒泡触发的 404 页「返回首页」卡死
PostDetail 对不存在的 slug 抛出 ServerFnError(404) 后,ErrorLayout 的
ErrorBoundary 捕获错误并在 fallback 渲染 NotFound。此时 ErrorBoundary 持有错误,
根据 Dioxus 0.7.9 的渲染逻辑(有错则永远渲染 fallback,不渲染 children/Outlet),
点击 Link 切换路由虽更新了 URL,但页面仍停留在 fallback,表现为「URL 变了页面不变」。
根因:ErrorBoundary 捕获错误后不会自动恢复,必须显式调用 clear_errors() 才能
重新渲染 children。原实现用 Link 导航无法干预点击时机清除错误。
修复:NotFound 与 ErrorLayout 的通用错误 fallback 中,「返回首页」改用 button,
onclick 内先 clear_errors() 清除错误边界,再 navigator().push 导航。
- NotFound 通过 try_consume_context::<ErrorContext>() 获取可能存在的错误边界:
路由匹配命中的 404 无错误(clear_errors 是 no-op),错误冒泡的 404 才需清除。
- ErrorLayout 的 handle_error 闭包参数本身就是 ErrorContext,clone 后在按钮内使用。
|
2026-07-24 10:34:18 +08:00 |
|
|
|
f9cf1f71e4
|
chore: release v0.6.1
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
mhchem panic 修复 + 非测试代码 unwrap 消除的 patch 发布。
### Changed
- 消除非测试代码中的裸 unwrap(),改写为带不变量说明的 expect()
### Fixed
- mhchem 转译器三处 panic(正则转录、多字节字符越界、re! 宏降级)
- Docker 构建缺 patches/ 目录导致 pnpm install ENOENT
v0.6.1
|
2026-07-23 18:29:54 +08:00 |
|
|
|
d80664db5e
|
docs(agents): 新增规范 #16——非测试代码禁止 unwrap
panic="abort" 让进程级 panic 无法恢复,裸 unwrap 与之冲突。新增第 16 条
明确:
- 默认走 ? / Result,经 AppError 或 Option 映射失败
- .expect("reason") 仅用于真正的不变量,消息必须解释为何不可能失败
- LazyLock 编译期常量、WASM 浏览器上下文、#[cfg(not(server))] stub、
bin 工具与 #[cfg(test)] 为豁免集
- 预留 clippy unwrap_used/expect_used lint 的 allow 范围
|
2026-07-23 18:26:24 +08:00 |
|
|
|
690e00e194
|
refactor(unwrap): 消除非测试代码中的裸 .unwrap(),改为带不变量说明的 .expect()
panic="abort" 下任何 panic 直接杀进程,与 unwrap 的「稍后处理」语义冲突。
本轮把所有可证不变量的裸 unwrap 改写为带说明的 expect,并修复 time.rs 中
u32→i32 转换在大 ms 值下的潜在溢出 panic(clamp 到 i32::MAX)。
- src/utils/time.rs: sleep_ms 的 ms.try_into().unwrap() 改为 clamp 到
i32::MAX,避免超大延时值溢出;window/set_timeout 的 expect 补充上下文说明
- src/utils/text.rs: 6 个静态正则的 unwrap 改为 expect,统一说明「编译期校验」
- src/api/{auth,comments/helpers}.rs: EMAIL_REGEX 同上
- src/api/markdown.rs: IMG_RE / TABLE_RE 同上
- src/api/rate_limit.rs: NonZeroU32::new(val.max(1)) 与 Quota::with_period
补充不变量说明
- src/api/image.rs: HeaderValue::from_str(&etag).unwrap() 改为 expect 说明
etag 仅含 ASCII hex;复用 etag_value 避免 clone
- src/middleware.rs: 静态 302 重定向响应的 unwrap 改为 expect
- src/components/post/post_content.rs、src/theme.rs: WASM 上下文 window().unwrap()
改为 expect 说明仅浏览器执行
保留的 unwrap/expect(合理不变量,已在注释中说明):
- DB_POOL LazyLock 闭包(已有详细注释 + validate_database_url 前置校验)
- csrf.rs 静态 forbidden 响应
- highlight.rs syntect 内置 Plain Text 语法
- bin/generate_highlight_css 构建工具
- comments/* 的 #[cfg(not(server))] unreachable!() WASM stub
|
2026-07-23 18:26:19 +08:00 |
|
|
|
d1013dfa08
|
refactor(mhchem): re! 宏编译失败降级为不匹配,补上 panic=abort 缺失的防御
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
re! 宏原先 LazyLock::new(|| Regex::new($pat).unwrap()),在 panic="abort" 下
任何正则转录错误(如曾经的 ". __* " InvalidClass)都会直接杀进程,且 to_tex
的 catch_unwind 在 abort 模式下无法兜住。
引入 compile_or_log(pat) -> Option<Regex>:编译失败记 tracing::error 日志
并返回 None(绝不 panic)。封装为 CompiledPat(Option<Regex>) newtype,把
「坏正则降级为不匹配」的语义集中在一处:
- captures_head(input) -> Option<Captures>:坏正则/未匹配统一 None
- is_match(input) -> bool:坏正则/未匹配统一 false
re! 宏改为产出 &CompiledPat,调用方直接 re!("...").captures_head(...) /
.is_match(...),无需再到处写 re_ref(...)? 或手写 None 回退。状态机遇到
坏模式自然退到 "else" 兜底分支产出原样字符,不影响整体渲染。
适配点:Pat::Re、pat_match_head、match_pattern 的 re_match 闭包、
pat_enumber/state_of_aggregation/amount/formula、1/2 动作、mhchem_tables.rs
的 ce_action、3 个 static LETTERS/GREEK/ONE_GREEK_RE。
回归测试 compile_or_log_degrades_bad_regex_without_panic 断言坏正则返回 None
不 panic。clippy 干净,622 测试全过。
|
2026-07-23 18:16:22 +08:00 |
|
|
|
a03fbac88c
|
fix(mhchem): 修复 find_observe_end 逐字节扫描多字节字符的 panic
find_observe_end 用 `let bytes = input.as_bytes(); let mut i = start; while i < len { ...; i += 1; }`
逐字节步进,对每个位置做 `&input[i..]` 切片与 `bytes[i] as char` 花括号判定。
化学公式若含多字节 UTF-8 字符(如中文「浓」占 3 字节),i 会落在字符内部:
- `&input[i..]` 触发 `byte index N is not a char boundary; it is inside '浓'` panic;
- 在 panic=abort 下直接 SIGABRT 杀进程;
- `bytes[i] as char` 把多字节字符首字节误判为花括号,还静默损坏输出。
触发路径:rebuild_content_html 重建含中文化学式(如 \ce{浓H2SO4})的文章时崩溃。
修复:改用 `input.char_indices().skip_while(...)` 按字符边界步进,i 始终落在
char 边界上,`a` 直接是解出的 char。start 校验对齐到边界后跳过。
新增:
- 回归测试 ce_multibyte_char_in_braces_does_not_panic:覆盖触发输入。
- 保护性测试 ce_arbitrary_multibyte_does_not_panic:穷举 emoji/日韩文/组合字符/
四字节区,覆盖花括号/方括号/\frac/$...$ 全部走 find_observe_end 的路径。
注:多字节字符的状态机转译输出质量(如 ce("浓H2SO4") 的字符重复)是独立的
既有问题,mhchemParser 本为 ASCII 化学符号设计;本提交只修 panic,不扩大范围。
|
2026-07-23 18:00:41 +08:00 |
|
|
|
feaf76bcd9
|
fix(mhchem): 修复 ". __* " 正则转录错误导致的 panic=abort 崩溃
mhchem 转译器中 ". __* " 模式的 regex 被误抄成
^([.\u{22C5}\u{00B7}\u{2022}|[*])\s*
即把 alternation 的 `|` 放进了字符类内部。fancy-regex 把 `[...|[*]`
当作单个畸形字符类,报 ParseError(20, InvalidClass);`re!` 宏的
`.unwrap()` 因此 panic。
上游 mhchemParser 4.2.2 是 `[...]|[*]`(alternation 在类外),修正为
^([.\u{22C5}\u{00B7}\u{2022}]|[*])\s*
触发路径:用户提交含 `\ce{·}`/`\ce{•}`/`\ce{.}` 等化学式的文章 →
render_markdown_enhanced → katex::render_* → expand_chem → mhchem::ce,
首次编译该 LazyLock 正则即 panic。
关键放大因素:本项目 release profile 为 panic="abort",`to_tex` 内的
`std::panic::catch_unwind` 在此设置下无法捕获 panic,进程直接 SIGABRT。
修正了 `ce`/`pu` 的文档注释,不再谎称"绝不 panic"。
新增:
- 回归测试 ce_dot_bullet_bond_inputs_dont_panic:覆盖触发输入。
- 保护性测试 all_match_patterns_compile:强制编译 match_pattern 全部分支,
任何同类转录错误在测试期而非生产期暴露。
|
2026-07-23 17:49:20 +08:00 |
|
|
|
3e9a3aa70d
|
docs(mhchem): 修复模块级文档中的 broken intra-doc links
cargo doc 在 --document-private-items 下报 7 个 unresolved link 警告,
全部源自 src/api/mhchem.rs 的模块级 //! 文档:
- 公开 API ce/pu 用简写 [`ce`] 无法解析,改用全路径链接
[`ce`](crate::api::mhchem::ce) / [`pu`](crate::api::mhchem::pu)。
- 私有项 Buffer/Parsed/Field/find_observe_groups/build_transitions 即便
写全路径也无法被 intra-doc link 解析器识别(rustdoc 对私有项的限制),
改回普通反引号代码,不再生成链接。这些是模块内部实现细节,本就无需
跨项跳转。
修复后 make doc 零警告。
|
2026-07-23 17:48:06 +08:00 |
|
|
|
089fe88a75
|
fix(docker): 复制 patches/ 目录以修复 pnpm install ENOENT
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
pnpm-workspace.yaml 声明了指向 patches/@tiptap__markdown@3.27.3.patch 的
patched 依赖,但 Docker 构建在 COPY package manifest 阶段未包含
patches/ 目录,导致 pnpm install --frozen-lockfile 因找不到 patch
文件而失败。将 libs/patches/ 加入 COPY 即可。
|
2026-07-23 16:45:00 +08:00 |
|
|
|
970ff8dc5b
|
chore: release v0.6.0
v0.6.0
|
2026-07-23 16:33:07 +08:00 |
|
|
|
f2216119ae
|
chore(markdown): 格式化测试中的长字符串字面量
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
|
2026-07-23 16:30:31 +08:00 |
|