Files
pure-note/docs/acceptance.md
T
wangairnan 9b0aff6f9f docs: 新增编辑器即时渲染方案、实施与 P3 加固记录
- editor-live-rendering.md(新):Typora 式单编辑面方案——路线选型、装饰层分层设计、
  widget 规格、安全与风险、测试方案、P0–P3 实施拆解;§12 为实施与加固记录:
  两个实测缺陷的根因与修复、3000 行性能实测数据、§12.6 真机输入法人工门槛(阻塞发布)、
  §12.7 编辑区高度
- design.md §8.2:编辑器段落由「分屏实时预览」改为即时渲染表述,链接本方案并标注发布门槛
- decisions.md:D37–D52(装饰器路线、单一 ViewPlugin、行级 reveal、长文档补解析、
  选区端点行采集、reveal 二分、@codemirror/commands 仅为 devDependency、人工门槛、编辑区高度)
- acceptance.md:新增 M5 里程碑与 M5-G 人工门槛行,补 v1.1/v1.2 浏览器复走记录与用例数

文档先行,代码实现见后续提交。
2026-09-11 01:02:36 +08:00

11 KiB
Raw Blame History

最终验收报告

依据:docs/design.md(v1.1);决策记录:docs/decisions.md 日期:2026-09-08

一、验收结论

全部通过。 设计文档定义的功能(§2.1 F1–F8)、安全设计(§9)、测试策略(§13) 与里程碑验收门(§12 M0–M4)均已实现并有自动化测试或实测证据支撑。 go vet / govulncheck / npm audit 干净;全量测试绿;构建冒烟 19 项断言全过; 真实浏览器端到端走查(博客浏览 / 登录 / 编辑器 / 回收站)通过。

二、§9.3 上线前安全检查单(逐项)

检查项 状态 证据
go vet + govulncheck 干净;npm audit 干净 ✅ go vet ./... 通过;govulncheck ./...:0 个影响本代码的漏洞;npm audit --omit=dev:0 vulnerabilities
可见性矩阵自动化用例全绿(列表/详情/标签/RSS/sitemap/图片直链/HTML meta/管理接口) ✅ TestVisibilityMatrix(表驱动:{匿名,管理员} × {公开,私有,回收站,不存在} × 7 出口 + 图片缓存头分流 + 404 防枚举一致性)
恶意 Markdown 渲染无脚本执行 ✅ 前端 vitest 冒烟(script/iframe/事件属性/javascript:/style/SVG 全拦截 + schema 快照);服务端 goldmark 转义 + bluemonday(TestRenderEscapesDangerousHTML);CSP script-src 'self' 兜底(冒烟断言)
跨站伪造 POST(含 login/logout)被拒;Cookie 属性与 __Host- 前缀;刷新后 CSRF 经 /api/me 重取 ✅ TestCSRF(无 token/错 token/跨源 login/logout/缺 Origin+Referer);TestLoginFlow(/api/me 返回 csrf_token);实测 Set-Cookie:__Host-pn_session=…; Path=/; Max-Age=604800; HttpOnly; Secure; SameSite=Lax
伪造扩展名/SVG/超限文件被拒 ✅ TestUploadValidation(魔数不符 415、SVG 拒绝、声明类型白名单、空文件 400、>5MB 413、同字节去重同 id)
登录爆破触发 429;失败日志含 IP ✅ TestLoginRateLimit(第 6 次失败 429 + Retry-After、正确口令在桶空时同样 429、成功不计费);实测日志 login_failed ip=127.0.0.1 ip_bucket_left=9 account_bucket_left=4
settings 响应不含口令哈希;PUT 无法写非白名单键;改密通过 ✅ TestSettingsWhitelist(GET 不含 password/argon2、未知键 400、page_size 生效);TestPasswordChange(旧错 401/弱新 400/成功/旧口令失效/当前会话保持)
图片:私有与不存在统一 404;缓存头 immutable vs no-store ✅ TestVisibilityMatrix 图片出口(公开 immutable / 非公开 no-store / 匿名 404 响应体一致防枚举)
安全头逐项确认(含 HSTS) ✅ 冒烟脚本断言 HSTS/X-Frame-Options/nosniff/CSP;全套头由 SecurityHeaders 中间件统一下发(curl 实测)
备份还原演练(停服→删 wal/shm→替换 db→恢复);备份 0600 ✅ 演练实测:备份文件 -rw-------(0600);删 wal/shm → 换备份 → 起服 → 数据完整可读
回收站恢复用例;gc dry-run→commit;7 天内孤儿图片不删 ✅ TestTrashLifecycleAndGC + CLI 实测(dry-run 报告、commit 执行、宽限期保留)
user_version 越界启动被拒;--allow-newer 放行 ✅ TestMigrationNewerRejected + CLI 实测(拒启 exit=1;--allow-newer 后 /api/health 200)
构建冒烟测试通过 ✅ make smoke(npm ci → vite build → sync-assets → CGO_ENABLED=0 交叉构建 → 起服务 19 项断言:SPA 资源 200+MIME、meta 注入、安全头、私有不可见)
Caddy 仅开放 80/443、程序监听 127.0.0.1 ✅(配置交付) deploy/Caddyfile + pn.service(--addr 127.0.0.1:8080 --behind-proxy)——本机无生产部署环境,按配置文件验收

三、§13 测试策略落地

测试组 用例 位置
可见性矩阵 表驱动全出口(§13 矩阵全覆盖,含图片并集语义与缓存头分流) internal/httpapi/visibility_test.go
迁移 空库→最新幂等;越界拒启;allow-newer 放行;外键级联 internal/store/store_test.go
认证会话 登录成功/失败统一文案、429、改密三态、轮换后 CSRF 不变、/api/me 两态、登出 internal/httpapi/api_test.go
CSRF 无/错 token 403、跨源(含 login/logout)、缺 Origin+Referer 同上
上传 魔数/伪造扩展/SVG/超限/空/去重 同上
回收站 删→不可见→恢复→可见;图片引用保全;30 天 gc;7 天宽限 同上
gc dry-run 不改数据、过期会话清理 同上
slug 中文 post-YYYYMMDD、同日冲突 -2、回收站占用、手改 409 字段级错误、非法格式 400 同上
元信息注入 公开注入笔记 meta;私有/不存在回退站点默认;html/template 转义 visibility_test.go + webui_test.go
前端 vitest sanitize schema 快照 + 恶意 Markdown 冒烟 + GFM 回归 + node prop 泄漏检查 web/tests/markdown.test.tsx(6/6)
即时渲染装饰层 语法区间识别 / reveal(含二分等价)/ 安全 / 文首文末边界 / 跨原子区间选区 / 不变量 / 3000 行性能基准 web/tests/editor-live-preview.test.ts(29)+ editor-boundaries.test.ts(32)

回归命令:make test(go vet + go test ./... + vitest)与 make smoke。

四、里程碑验收门(§12)

里程碑 验收门 状态
M0 骨架 make build 产出可运行二进制;start 起服务;/api/health 200 ✅
M1 公开浏览 笔记/标签 API + 博客三页 + 渲染管线 + RSS/sitemap + meta 注入;公开侧可见性用例;构建冒烟 ✅
M2 管理端 登录/会话/CSRF + CRUD + 回收站 + 图片上传 + 编辑器 + 改密;完整矩阵 + 各组用例随功能交付 ✅
M3 安全加固 §9.3 全项通过(见上表);CSP nonce 硬化按设计留 M3+ 待办 ✅
M4 运维 systemd ×3 + Caddyfile + 备份恢复演练真还原 ✅(压测 hey 未做:单机个人规模非验收必需,工具未安装;SetMaxOpenConns(1) 串行模型按设计实现)
M5 编辑器即时渲染(P0–P3) 编辑态=阅读态排版(--md-* 契约)、全语法即时渲染、光标行展开/失焦整篇、任务框回写源文本、源码模式逃生舱;边界与选区回归(web/tests/editor-boundaries.test.ts 32 条);3000 行性能基准达成 §7.4 的 <16ms 目标 ✅ 代码与自动化(实测数据见 editor-live-rendering.md §12.5)
M5-G 人工门槛:真机输入法 系统拼音 / 搜狗拼音按 §12.6 的 I1–I8 用例走查并签字 ⛔ 未执行 —— 阻塞发布

五、真实浏览器走查(补充验收)

以内置浏览器对 make build 产物做黑盒走查,全部通过:

  1. 博客首页:暗色主题、站点标题、置顶排序、标签云计数、页脚 RSS/Sitemap;
  2. 详情页:面包屑、标题/日期/标签、GFM 表格与任务列表(disabled checkbox)渲染、上一篇/下一篇;
  3. 登录:错误口令提示、成功跳转 /admin;
  4. 管理列表:公/私徽标、slug/时间/标签、状态切换/编辑/删除;
  5. 编辑器:CodeMirror 输入、分屏实时预览(v1.1 起改为即时渲染,方案见 editor-live-rendering.md;源码模式为逃生舱)、保存后自动生成 slug("go")、摘要自动截取、URL 迁移到 /notes/3/edit;
  6. 回收站:删除确认弹窗 → 列表移除 → 回收站出现 → 恢复 → 公开页重新可见(标签页标题即 meta 注入验证)。

v1.1 编辑器即时渲染复走(2026-09-11)

在 pn start --dev + vite dev 上对内联渲染逐项复走:标题/粗体/斜体/删除线/行内代码/链接/嵌套引用/ 三级无序圆点/有序序号保留/任务框/图片(原尺寸+圆角)/水平线/围栏语言标签与底色/表格等宽淡化均按预期; 光标行展开源码、失焦整篇渲染;点击任务框改写源文本并自动保存;源码模式反复切换无残留。 细节与剩余人工项见 editor-live-rendering.md §12。

v1.2 P3 加固复走(2026-09-11)

  • 边界:文档以标题/任务项/围栏/水平线开头、以水平线/图片/围栏/任务结尾均正确渲染;整篇只有一条水平线可用; 文首连续退格不损坏内容;文末追加内容后装饰正确重建。
  • 跨原子区间选区:3000 行文档 Cmd+A + 退格清空、Cmd+Z 完整恢复;跨隐藏标记的选区替换不残留标记碎片; 只选中可见内容时标记完整保留。
  • 性能:3000 行 / 15.6 万字符文档,视口装饰采集中位数 0.26ms、全篇最坏 5.0ms、最坏选区(Cmd+A)0.98ms、 真实输入同步耗时中位数 0.1ms、冷启动直跳文末最大帧间隔 7.1ms 且零 long task(详见方案 §12.5.2)。
  • 修复:长文档惰性解析导致未解析区间显示裸 Markdown(已修,方案 §12.5.1 P3-1); Cmd+A 全文档装饰构建与线性 reveal 扫描(已修,P3-2)。
  • 编辑区高度(2026-09-11,用户要求):编辑卡默认吃满视口剩余高度、长文改为编辑器内部滚动。 1280×720 下空文档与 3000 行长文的编辑卡均为 610px;长文页面保持一屏(此前 8.4 万像素), .cm-scroller 内部可滚;窄屏 ≤900px 回落内容高度。方案 §12.7。

⛔ 未通过的人工门槛(阻塞发布)

真机输入法回归(系统拼音 / 搜狗拼音)尚未执行,用例、环境要求与签字要求见 editor-live-rendering.md §12.6。在签字通过前不得发布。

六、交付物清单

pn(单二进制,CGO_ENABLED=0,go:embed 前端产物)
├── cmd/pn/          start / init / passwd / backup / gc / version
├── internal/{config,store,auth,markdown,middleware,httpapi,webui}
├── web/                    React 19 + Vite 8 + Tailwind 4(vitest 71 用例)+ editor/ 即时渲染装饰层
├── deploy/                 pn.service、pn-maint.{service,timer}、Caddyfile
├── scripts/smoke.sh        构建冒烟(make smoke)
├── Makefile / README.md / .gitignore
└── docs/{design.md, review-round1.md, decisions.md, acceptance.md}

依赖面:Go 直接依赖 6 个(预算 ≤6);npm 生产依赖与设计一致(运行时零新增),0 漏洞; 新增 @codemirror/commands 为 devDependency(本就在依赖树中,用于边界回归真实执行编辑命令)。 锁定文件 go.sum / package-lock.json 入库。

七、遗留事项(不阻塞验收)

  1. CSP style-src nonce 硬化 —— 设计明示 M3+ 待办(§9.2/§14),v1 保留 'unsafe-inline';
  2. hey 压测 —— M4 可选项,需生产规格环境;
  3. vite dev 开发态标签页标题显示模板占位符(D23,仅开发态外观);
  4. shadcn CLI 未使用,按「复制式组件」理念手写等价最小组件集(D22)。