Releases: AAAAAnson/mbeditor
Release list
v5.6.1 — 交互效果点击修复 + 微信自触发改造
真机真值表驱动的交互修复 patch:让交互效果在编辑器里点得动、在微信真机上(id 被剥后)也点得动。
后端测试 648 passed / 15 skipped,前端 369 passed。
🐛 修复:tab-panel 点击展开无反应
标签 <text> 盖在按钮 <rect> 上且为兄弟节点,点击文字时事件不经过触发元素 → begin="tabNbtn.click" 收不到(真实浏览器复现)。改为 <g id> 包裹 rect+label,对齐其余效果结构。预览面新增提示:检测到可交互 SVG 且在「公众号效果」预览时,引导切到「交互预览」点击测试(不影响编辑内容)。
🔧 交互效果改造为微信自触发(消除跨元素 id 依赖)
真机真值表确认:微信剥光所有 id=,却保留 begin="id.click" 引用 → 跨元素触发在公众号真机悬空失效。改为同元素 begin="click"/"touchstart" 自触发 + 覆盖揭示。全部 6 个交互效果经「微信实际存储 HTML + 真实 Chromium 点击」验证,剥 id 后交互均存活:
| 效果 | 改造 | 语义 |
|---|---|---|
| smil-carousel / mask-reveal | 直接改自触发 | 完整保留 |
| flip-card | 覆盖揭示(背面常驻、正面淡出) | 降级单向翻面 |
| longpress-ring | 充环+延时盖板淡出自触发;圆心可命中 | 长按近似 |
| tab-panel | 覆盖揭示手风琴 | 受限:各板块独立展开、不互斥 |
| multi-choice | 选项自高亮、解析常驻 | 受限:不按选项切换 |
| scroll-carousel / pano-slide | 纯 CSS scroll-snap | 不受影响 |
tab-panel / multi-choice 的「点 A 控制独立 B」本质需跨元素、微信无 id 做不到,已诚实判定受限并降级(不保留悬空 id.click 假装能用),description 与模板注释均标注。
✅ 测试
新增参数化断言:渲染通过 / begin·end 无跨元素 id 引用 / 模拟剥 id 后存活 / 不触发 id-stripped-dangling-ref 告警。
完整条目见 CHANGELOG.md。
🤖 Generated with Claude Code
v5.6.0 — 微信 SVG 交互能力路线图(P1+P2)+ 真机真值表
本次发布完成微信公众号 SVG 能力路线图的全部 P0 → P1 → P2:从"自家管线先把 SVG 打坏",到"AI 填槽即产出过校验的合法交互 SVG",并首次用 31 探针真机真值表校准了规则边界。
后端测试 250 → 619 passed,前端 282 → 369 passed。
✨ 新增
- 交互效果引擎(P1-1):8 个 opensvg 派生插槽化配方,覆盖六类交互(展开/轮播/滑动/长按/答题/翻卡),文本/图片/颜色槽 + 时序参数,填槽渲染强制过校验;支持区块级插入(不再整篇覆盖)。新端点
GET/POST /api/v1/agent/effects。 - 真后端 LLM 生成(P1-4):接入 Claude API(
claude-opus-4-8)。LLM 只产结构化 DSL、绝不直接写 SVG,经 effect registry 确定性渲染——注入面与校验面全部收敛。无 API key 时优雅降级。 - 编辑器内交互预览(P1-3):「公众号效果」(sanitize 后) ↔「交互预览」(原始 SVG,sandboxed iframe,SMIL 点击可真实验证) 模式切换。
- 反向导入 + 多平台 profile(P2-4):微信文章/通用 HTML 反向解析为 mbdoc(SVG 子树逐字保真),
POST /api/v1/publish/import-html;wechat(行为字节等价) +generic(更宽松) 渲染 profile。 - 编辑体验(P2-1/2/3):SVG 可视化面板(颜色/热区/SMIL 时序)替代纯 textarea;模板缩略图 + 参数化配色文案;StructurePanel 解析 SVG 内部交互结构并可点击定位。
🔬 真机真值表(关键发现)
31 探针经 add_draft 真推草稿箱 + 回读,产出 docs/research/wechat-svg-truth-table.md。
微信剥除
id=定义,却保留url(#id)/href=#id/begin=id.click引用 → 一切依赖 id 的渐变/滤镜/clipPath/mask/use/跨元素 SMIL 在真机悬空失效,仅begin=click同元素自触发可用。
该结论已转化为校验器 id-stripped-dangling-ref 告警,从生成阶段拦截。
🛠 校准与安全
- 白名单按 T/CASME 1609—2024 + 真值表校准:
stroke/rx/ry放行、静态stroke-dasharray不再误杀;新增<style>/SVG<a>/外链<image>三类告警;事件处理器拦截改为通配on[a-z]+=。 - LLM 输出、反向导入 HTML、模板槽值全部视为不可信,双重门禁;对抗式审查修复跨类槽位二次替换、
url(javascript:)样式注入、<image>危险 scheme 等多条注入路径。安全底线未回退。
完整条目见 CHANGELOG.md。
🤖 Generated with Claude Code
v5.5.0 — 页面背景修复 + 复制管线加固 + MBDoc API
🎨 重点修复:页面背景粘到公众号后台不再消失
根因终于找到了:微信粘贴处理器会"解包" HTML5 语义标签(<article> <main> <header> <footer> 等)——子内容保留,但标签连同它的 style 属性一起被丢弃。把页面渐变/底色写在 <article style> 上的推文,粘贴后整页变白。
- ✅ Sanitizer 把语义包裹标签统一改名为
<section>(微信完整保留),背景随之存活 - ✅ 校验器新增
semantic-wrapper-tag警告,复制前就提示 - ✅ 新增全文档管线回归测试,以真实推文结构断言背景存活
🛠 复制管线全面加固
完整 HTML 文档支持:AI 直接产出的带 <!DOCTYPE> / <head> / <body bgcolor> 的完整文档,现在能正确走完复制管线——
<!DOCTYPE>不再让 premailer 抛异常跳过 CSS 内联<title>/<meta>/<link>不再泄漏进正文<body>的 style/bgcolor 自动搬到根<section>上,针对body的 CSS 规则同步重定向- 嵌套
@media整块剥除(旧版会让移动端display:none泄漏成全局规则,所有端隐藏内容) a, a:hover {...}这类混合选择器只删伪类部分,普通选择器保留- 单引号 style 内含双引号(
font-family:"PingFang SC")不再被截断
校验与图片:
- 正文里出现 "mask" 等英文单词不再误触发禁用 CSS 拦截
- 图片 data-URI 内联锚定到
<img>标签,教程正文/<video>/<iframe>不再被误改写;失败 URL 负缓存免重试
前端剪贴板:
- 长文分段复制不再静默丢失裸文本节点
- swiss 主题不再误剥作者写的黑色/深灰背景(深色 hero、代码块幸存)
- 纯文本兜底按块级边界换行,不再全文挤成一行
📦 新增:MBDoc 后端 API
- MBDoc CRUD + 发布端点(
/api/v1/mbdocs),前端 store 接后端并保留本地缓存层 - Article → MBDoc 转换器与一键迁移脚本
- EditorSurface / publish_adapter 重构为 hook + service 分层
- 后端 API 测试基础设施 + publish 集成测试;文章列表 JSON 导出/导入备份
✅ 质量
后端 250 个测试通过,前端 282 个测试通过,Vite 构建干净。
Full Changelog: v5.4.0...v5.5.0
v5.4.0 — Chunked Copy + Title URLs + Sanitizer Table Fix
Added — 长文一键复制
- "复制富文本"对话框新增长文模式:HTML > 400KB 时弹"分段复制 / 改用草稿箱"选择面板。分段路径按 body 顶层 block 边界递归切到 ~250KB 一段,每段都是自洽的富文本片段,引导用户逐段粘到公众号后台末尾。
- 切分算法
splitHtmlIntoChunks递归下钻:遇到超预算的元素就钻到它的子级,每一层切完都用本层的开/闭标签把内容重新包起来,保证嵌套结构里的标题/卡片/章节等外层样式在每段粘贴时都还在。 - 草稿箱兜底:未绑定公众号 appid+appsecret 时按钮灰掉并提示,绑了就一键跳
/wechat/draft完全跳过剪贴板。
Added — 文章 URL 与标题对应
- 路由从"
/一招走天下"换成/a/<encodeURIComponent(title)>-<4位ID>的标题型 URL。浏览器地址栏现在能看出打开的是哪篇文章,可收藏可分享。 - 标题改名时通过
replaceParams原地同步 URL,不污染后退栈。 - nginx 已有
try_files \$uri \$uri/ /index.html,刷新/a/<slug>直接命中 SPA fallback,无需服务端配置改动。
Fixed — 微信粘贴布局
- Sanitizer 注入
table-layout:fixed:当<table>里至少一个直接子<td>写了width:Xpx,就给该表加table-layout:fixed,让作者的列宽真正生效。默认table-layout:auto会让浏览器把窄列压到 single-character min-content,正是"01""02"等数字徽章被分裂到两行的根因。 - 同步规则:表本身没设 width/max-width/min-width 也没
width=HTML 属性的,自动补width:100%——HTML5 规范里 fixed-layout 在无宽度表上等于 auto,必须给一个宽度才能生效。 table-layout加进 sanitizer allowlist,值限定{fixed, auto}。
Backend 202/202 tests passing · Frontend 232/232 tests passing
v5.3.0
v5.0.0
Added — 编辑器所见即所得
- 预览区现在是可编辑的
contentEditable画布:直接在预览里改文字,500ms 去抖后自动回写到 HTML/Markdown 源码。形状一致时只拷贝文本节点,保留源码结构;形状漂移走 sanitizer 兜底。 - HTML→Markdown 序列化器覆盖标题/段落/嵌套列表/引用/代码块/分隔线/图片/inline 强调/链接,Markdown 模式的预览编辑能无损回写。
- 预览框支持拖拽调整宽/高/右下角,以及 40%–200% 独立缩放滑杆;尺寸和缩放分别持久化到
uiStore。
Added — 五套示例模板 + 一键复制富文本
- 内置
极简商务 / 科技霓虹 / 活力撞色 / 文艺手札 / 杂志专栏五种风格示例文章,全部 100% 微信 sanitizer 白名单内联样式。 - 恢复并增强了一键复制富文本按钮,复制结果与预览完全一致,粘贴到公众号后台 0 样式损失。
Added — 编辑器导航与结构面板
- 左侧 StructurePanel 列出标题/图片大纲,点击节点自动跳到对应位置并在预览和编辑器中同步高亮。
- 编辑器头部新增「返回上一页 / 返回稿库」回退按钮,保留未保存草稿。
Changed — UI 与体验大改
- 前端重做:
walnut / paper / swiss三主题 + 三种布局(focus / split / triptych)。 - 去掉了产品里暴露的 Agent Console(保留为 marketing 素材,不进打包)。
- 非关键英文 UI 文案改成中文(导航、按钮、状态芯片)。
- 关闭自动保存时保留草稿,切换文章不清空未保存内容。
Changed — 后端
- 数据目录自动探测:检测到
docker-compose.yml走仓库根data/,否则落backend/data/,写死的/app/data去掉。 - 启动时统一
ensure_data_directories()创建 images / articles / mbdocs / config.json 父目录。 PUT /articles/{id}在mode=markdown且只收到 Markdown 时自动用 markdown renderer 同步 HTML,便于 CLI/API 调用方只传 Markdown。
Added — 文章列表删除按钮
- 列表每行末尾新增 🗑 按钮,点击触发
window.confirm→DELETE /articles/{id};同步清理currentArticleId,删当前编辑中的文章不会留下悬挂引用。
Added — 首次启动自动 seed 五篇模板
- backend 启动若检测到
ARTICLES_DIR为空,从backend/app/seeds/tpl_*.json把五套示例模板种进去;已有内容绝不覆盖。模板同时保留在仓库根docs/cli/examples/templates/,便于 dev 模式和 CLI 复用。
Security — 预览粘贴的 XSS 护栏
cleanPreviewFallback从「只剥 style/class/contenteditable」升级为完整黑名单:<script>/<iframe>/<object>/<embed>/<link>/<meta>/<style>/<base>/<frame>/<frameset>直接移除;on*内联事件处理器一律 strip;href/src/xlink:href中的javascript:/vbscript:/data:text/html协议一律清除。保留父元素文本和安全属性。render_markdown_source关闭 markdown-it 的html: true,Markdown 里内嵌的<script>/<img onerror>一律被当作文本转义输出,不会以活 HTML 写入article.html。publish 管道仍会做一遍 sanitize,但现在存储态本身也是安全的。
Changed — 版本号
- backend
pyproject.toml、APP_VERSION、frontendpackage.json、BrandLogo、README 徽章、Settings 页面全部统一到5.0.0;测试用例改用APP_VERSION常量而非硬编码字符串。
v4.1 — agent-first CLI + WeChat-safe 白名单 sanitizer
亮点
🖥️ Agent-first mbeditor CLI(对齐 CLI-Anything 理念)
pip install -e ./backend得到可执行的mbeditor- 默认 direct 模式:直接读写本地
data/文件 + 调用服务函数,不需要跑 FastAPI - HTTP 模式:
--mode http走/api/v1后端代理,共享 token / 缓存 - 命令组:
article / doc / image / render / publish / config / skill / info - 稳定输出 envelope:
{ok, action, message, data};--json --compact出 JSONL - 退出码约定:0 成功 / 1 执行错误 / 2 用法错误
- 随包
SKILL.md,mbeditor skill直接打印给 agent 吞 - 33 条 CLI 测试 + 1 条 CLI 驱动的端到端视觉零变形测试
🧼 WeChat-safe 白名单 sanitizer
针对"复制到公众号后台 vs /draft/add"渲染漂移的根因修复:
- 正向属性白名单(~60 项,基于真实公众号稳定文档校准)
- display 值约束:仅
block / inline / inline-block / none / table-*;flex / grid全部剥除 - position 值约束:仅
relative / static;absolute / fixed触发display:none - 去掉
!important/ flex 族 / grid 族 /float/clear/cursor/user-select/pointer-events/will-change - 保留
linear-gradient / box-shadow / letter-spacing / opacity / vertical-align等所有公众号真支持的视觉属性 - 按钮
<a>自动转<table><tr><td>结构
📖 附带示范文章 docs/cli/examples/demo_article.json —— 用新格式重写的 7-block 推文,25 个属性全部在白名单内,零漂样板。
🎨 真 Playwright raster 工作器
RasterRenderer发布时无 uploader 直接抛错,杜绝 data: URL 进草稿SvgRenderer发布时拒绝<image>的远程 href(含xlink:href)- 缓存 / 失败恢复 / 本地图片内联边界测试齐全
🧱 MBDoc stage 完成
heading / paragraph / markdown / html / image / svg / raster七种 block 全部有真渲染器publish.py剥离成路由层,transform 下沉到publish_adapter / legacy_render_pipeline / media_uploader / wechat_publisher等服务- 新增
document_projector:legacy Article ↔ MBDoc 桥接 /api/v1/mbdoc新增/project/article/{id}//render//publish端点
🧪 前端 block-native bridge 编辑器
- 新增
features/editor/模块,projected-block 编辑 + session 状态 - Lazy-loaded Monaco / Markdown 编辑器壳
- Vite chunk 切分:monaco / markdown-vendor / tiptap / react-vendor / lucide
- Editor chunk 降到约 62 KB
测试
- backend: 210 passed / 1 pre-existing xfail
- CLI: 33 passed
- WeChat-safe sanitizer: 26 passed
- 示范文章白名单守护: 7 passed
- 端到端 CLI-driven 视觉零漂移: 1 passed(diff = 0 px)
升级提示
- 如果你曾经在文章里依赖
display:flex/display:grid/position:absolute/animation/transform等做版式,它们现在会被 sanitizer 剥除。请用display:inline-block+vertical-align:middle做横排布局(参考docs/cli/examples/demo_article.json) - WeChat 凭据配置仍在
data/config.json,向后兼容 - 老的
/articles+/publishAPI 保留不变
v4.0 — 复杂 HTML 还原度 99.6%(WeChat publish pipeline 硬化)
🎯 一句话总结
结构 100%、几何 99.6% 地把一篇 600 行带 grid / SVG / scroll-reveal / CTA 按钮的复杂动画 HTML 推到公众号草稿——在 MB 科技测试公众号端到端验证通过。
背景
v3.1 之前 publish 流水线只能稳定处理简单图文排版。遇到复杂外部 HTML(例如 落地页导出的产品页)会出现:
- 内容大段空白(scroll-reveal 默认 opacity:0 依赖 JS 显示)
- Hero 高度暴涨 2-3 倍(装饰性
position:absolute失效变 block) - 下载按钮消失(
<a>标签被微信后端 strip) - grid 布局退化成单列堆叠(旧逻辑没保留)
v4.0 对 publish 流水线做了四项"硬化"修复 + 一整套视觉一致性校准基础设施。
✨ 主要改动
Publish 流水线硬化(backend/app/api/v1/publish.py)
| 输入 | 新流水线处理 | 结果 |
|---|---|---|
opacity:0 |
→ opacity:1 |
修复 scroll-reveal 隐藏 |
transform:translate*(...) |
→ transform:none |
修复 slide-up 偏移 |
transition* / animation* |
strip | 避免 sub-pixel 漂移 |
position:absolute|fixed |
→ display:none |
微信删 position,失效的装饰直接隐藏 |
<a href="外部"> |
→ <section> 保留样式 |
微信删 a,视觉按钮留下 |
首个外部 <a href> |
自动抽 → content_source_url |
公众号"阅读原文"链接 |
视觉一致性基线 20.96% → 1.47%
在 H1-H6 + 段落基线 doc 上把 pixel diff 从 20.96% 校准到 1.47%:
_BODY_STYLE_FLUSH完整镜像微信.rich_media_content容器(letter-spacing:0.578px、padding:0 4px、display:flow-rootBFC、contenteditable启用line-break:after-white-space)- 标题/段落
line-height改用整数 px(h1=36..h6=21、p=29)取代 1.4 / 1.8 倍率,避免分数 line-box 的累积 round 分歧 - 渲染器把文本包在
<span leaf=""></span>镜像微信 ProseMirror ingest 行为
新增校准基础设施
backend/tests/visual/dump_wechat_computed_styles.py— dump WeChat 容器 computed style 到 JSON(校准 ground truth)scripts/test_publish_html.py—/publish/preview+/publish/draft+ 截图一条龙scripts/test_publish_direct.py— 绕过 API 直接 import,对付 uvicorn 旧字节码scripts/compare_source_vs_draft.py— source ↔ draft 四象限 side-by-side
📊 验证结果(printmaster_wechat_animated.html 600 行测试)
| 阶段 | Draft 高度 | vs source 4564 |
|---|---|---|
| v3.1(旧流水线) | 7496 px | +64.24% ❌ |
| v4.0 修复 reveal | 5212 px | +14.20% |
| v4.0 修复 absolute | 4519 px | -0.99% |
| v4.0 最终 | 4547 px | -0.37% ✅ |
结构还原度 100%(文字、图片、SVG、grid、flex、按钮文字全保留)。
📖 文档
- 踩坑全记录:
docs/research/RESEARCH_CORRECTIONS.md— 完整校准过程 + 2026-04-12 publish 陷阱 writeup - Agent 使用指南:
skill/mbeditor.skill.md新增 "Publish Pipeline 已知陷阱" 章节,六个陷阱 + 预防建议 - Changelog:
CHANGELOG.md[4.0.0]
🧪 部署验证
已本地 docker compose 验证:
docker compose build # ✅ 镜像构建成功
docker compose up -d # ✅ backend healthy
curl http://localhost:7072/api/v1/version
# {"version":"4.0.0","repo":"AAAAAnson/mbeditor"}全流水线 smoke test(grid=4、display:none=7、position:absolute=0、<a>=0、opacity:0=0、cta-btn 保留)全部 assertion 通过。
⚠️ 已知问题
- Host-port shadowing:如果有 stale Python 进程绑定相同端口(如 7072),docker 部署会被僵尸 listener shadow。排查:
Get-NetTCPConnection -LocalPort 7072 -State Listen看谁占着 - 本地开发 uvicorn:必须加
--reload否则改publish.py后不生效 - 1.47% 剩余 sub-pixel 差异:WeChat 容器 letter-spacing 0.578px 的字符漂移,无法消除(也不影响视觉)
- 外部链接:每篇文章最多一个外部可点击 URL(通过"阅读原文"),写多个
<a href>只有第一个有效
🙏 感谢
感谢 Claude Opus 4.6 (1M context) 全程陪同校准 + 六次 WeChat 草稿 push 迭代的耐心 debug。
v3.1 — 预览所见即所得
What's Changed
Fixed
- 预览所见即所得 — 预览框改为显示后端处理后的内联化 HTML,复制到微信后台的效果与预览完全一致
- 移除 base CSS 中 section 全局 margin/padding 重置,防止覆盖文章自定义样式
- 复制时直接使用预处理好的 HTML,避免重复 API 调用
Full Changelog: v3.0...v3.1
V3.0 — 纯 HTML 排版组件 + 所见即所得发布
What's New in V3.0
核心变更
- 下架 SVG 交互模板:SVG+foreignObject 方案在部分微信客户端表现不稳定,暂时下架。相关代码保留,待方案成熟后重启
- 全新 HTML 排版组件:6 种纯 inline style 排版组件,复制到公众号后完美还原
- 标签徽章 / 渐变卡片 / 数据看板 / 时间线 / 引用样式 / 对比表格
- 所见即所得修复:后端自动注入 base styles(字体、字号、行高、颜色),预览效果 = 发布效果
改进
- 🐳 Docker 启动自动创建 data 目录,无需手动
mkdir -p data/images data/articles - ⚙️ 首页 Header 新增设置页面入口(齿轮图标)
- 📝 示例文章重新设计,展示纯 HTML 排版效果
- 📖 README 同步更新
快速开始
git clone https://github.com/AAAAAnson/mbeditor.git
cd mbeditor
docker compose up -d
# 访问 http://localhost:7073升级指南
已部署用户直接拉取最新代码重建即可:
git pull
docker compose up --build -dFull Changelog: v2.0...v3.0