Planning: parent design.md becomes the single authoritative contract (constraint model with 6 kinds, handle manifest, coordinate/unit layering, preview sequencing, storage layout and lazy migration, area config snapshot, API contract). Work is split into four independently verifiable child tasks with per-step gates and rollback points. Test infra: pin vitest 4.1.11, add test:client:unit for client pure logic, extend prettier globs to root *.ts so vitest.config.ts is checked. Add .gitignore: the repo had none, so inputs/, outputs/, workbench-data/ and the client build output were untracked rather than ignored. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9.4 KiB
9.4 KiB
实施计划
每步是一个独立提交,带自己的验证命令、门禁和回滚点。门禁不过就停在该步,不带着已知缺陷进入下一步。
约束与合约的权威定义在 design.md;本文只管执行顺序与验证。
全局回滚策略
- 任务分支上一步一提交,回滚等于
git revert该提交,不做跨步大回滚。 - 客户端编辑能力全程挂在
directEdit开关后,默认关闭,直到第 8 步手测清单通过才默认开启。开关关闭时工作台行为与当前 main 完全一致。 - 服务端新增端点与新文件是纯增量:
active/与revisions/不存在时,旧代码路径照常工作。任何一步回滚都不会让既有 import 目录无法打开。 - v1
native-road-overrides.json始终保持权威且格式不变,任何步骤都不迁移它。
0. 前置:依赖与测试基础设施
- 目标:先把验证能力建好,避免后续步骤"写完没法测"。
- 范围:
package.json增加vitest(固定版本)与test:client:unit脚本;建立客户端纯逻辑测试目录。不碰任何产品代码。 - 验证:
npm run test:client:unit(空套件通过)、npm run format:check、npm run test。 - 门禁:新脚本可跑通且不影响现有
test/test:client/build。 - 回滚点:仅
package.json与测试目录,revert 无副作用。
1. ol-ext 限时探针
- 目标:只回答一个问题——能否复用通用 handle 的命中、pointer 生命周期与视觉反馈。
- 范围:隔离探针,不修改生产基线图层,不进依赖清单直到通过。
- 验证:手动跑探针页;确认 OL
Map未重建、proxy 拖拽稳定、基线 source 未被写入、事件能转成 draft 值。 - 门禁:三条同时成立才把
ol-ext固定为依赖。超过一个工作日或任一条不成立,立刻停止排障。 - 回滚点:删除探针目录,改用原生 OL
Snap+ 小型PointerInteractionadapter。HandleManifest -> RoadEditOperation -> RoadConstraint -> preview solver合约不变,因此本步失败最多换输入层,不动 schema、求解器或已保存编辑。
2. v2 文档、活动副本与 revision 基础
- 目标:落地
native-road-edits/v2、operations、anchorSnapshot与 5 态重放状态机。 - 范围:新增文档读写、
active/与revisions/布局、内容寻址 OSM 副本、documentVersion、命名检查点与恢复读取。惰性迁移既有 import 目录,不移动既有文件。v1 overrides 完全不动。 - 验证:
npm run test(新增 fixture:文档往返、documentVersion递增、惰性迁移在只有旧文件的目录上生成rev-0001、内容相同的 OSM 不产生第二份副本)。 - 门禁:既有
workbench-data/import-*目录在新代码下可正常打开,且未被改写;schema 校验拒绝越界boundaryIndex与非法 station。 - 回滚点:revert 本步后
active/与revisions/只是残留目录,旧代码忽略它们照常运行。
3. area config 快照
- 目标:切断编译对外部区域配置文件的运行期依赖,让 revision 真正可复现。
- 范围:活动副本与每个 revision 各写一份
area-config.snapshot.json;编译与预览改读快照;/api/junction-clusters改写快照并在响应里返回可复制片段。 - 验证:
npm run test新增 fixture——冻结 revision 后修改外部 area config,重新编译该 revision 结果不变;快照缺失时给出明确错误而非静默用外部文件。 - 门禁:现有
/api/junction-clusters的用户可见行为(返回可复制片段)不退化。 - 回滚点:revert 后编译回到读外部配置文件;已写的快照文件被忽略,不影响 v1 链路。
4. 约束求解边界
- 目标:在
compileGeometry()前插入纯函数resolveDirectEditConstraints,输出 editable profiles、junction plans、handle manifest 与 diagnostics。 - 范围:实现 6 个 kind 的求解;interval 默认范围推导(避开两端 reserve);junction reserve 计算;
junction-approach-width对道路 profile 的优先级与边界连续过渡。米制换算复用src/geometry/lane-geometry.js,不新增第二套。 - 验证:
npm run test新增 fixture——每个 kind 的重放、最小车道宽 2.4m、左右外缘不交叉、道路/路口连续、路口面不自交、connector 包含性、boundaryIndex越界转stale、锚点缺失/歧义分别转stale/conflicted。 - 门禁:求解是纯函数,无文件写入、无网络;
npm run test全绿且既有 baseline 输出在无 v2 约束时逐字节不变。 - 回滚点:无 v2 约束时该步是恒等变换,因此 revert 前后编译输出一致,可安全单独回退。
5. 预览与编辑 API
- 目标:草稿预览与持久化分离,预览绝不写文件。
- 范围:
GET /api/edit-state、POST /api/edit-preview、POST /api/edits、POST /api/revisions、POST /api/revisions/:id/rebase;previewSeq回显;degraded标记;expectedDocumentVersion前置条件与 409;/api/state、/api/session、/api/import的增量字段。 - 验证:
npm run test新增 server integration——预览调用前后目录 mtime 与内容不变;expectedDocumentVersion不匹配返回 409 且不写入;原子写入在中途失败时不留半份文件;rebase 返回各 status 计数;预览与正式编译对同一约束集给出相同几何。 - 门禁:预览零写入;409 路径有测试覆盖;现有
/api/overrides、/api/compile、/api/state的既有字段与行为不变。 - 回滚点:新端点是增量,revert 后客户端开关已关闭,工作台回到 v1 行为。
6. 主地图 Road editor
- 目标:外缘、步行带、车道分隔与区间范围可拖,禁入 junction reserve。
- 范围:selection → handle manifest;handles / ghost / preview 三个独立 source;
EditSession命令栈与 undo/redo;80ms 防抖与pointerup最终请求;previewSeq乱序丢弃与AbortController取消;无效草稿反馈;应用/保存/取消。全部挂在directEdit开关后。 - 验证:
npm run test:client、npm run test:client:unit(命令栈与 undo/redo、乱序响应丢弃、handle 事件到约束值的投影、米制换算的纬度正确性)、npm run build。 - 门禁:单元测试覆盖上述四类纯逻辑;拖拽期间
Map未重建且只有受影响 source 被替换(手测清单第 1-3 项)。 - 回滚点:关掉
directEdit开关即可现场止损,无需回滚代码;必要时 revert 本步。
7. 最小 JunctionTools
- 目标:单一普通 node 路口的进口宽度、cutback、单角部圆角,闭合预览/应用/取消。
- 范围:单
JunctionRef会话的路由与返回主地图的上下文;session draft;完整局部预览;应用合并到活动副本;取消回到进入前状态;切换路口前强制应用或取消。不做 cluster 高级编辑、不做多路口联动。 - 验证:
npm run test:client:unit(会话状态机:draft → 应用 → 主工作区未保存约束;取消后状态复原;未处理草稿时切换被拒绝)、npm run test(局部求解与全量编译一致)、npm run build。 - 门禁:预览包含全部受影响派生对象(道路面、路口面、步行带、车道中心线、connector、停止线、斑马线、标线);拟合失败时返回最后有效几何加结构化诊断,不把非法图形显示成已应用。
- 回滚点:路由与开关独立,revert 不影响第 6 步的主地图编辑能力。
8. 全量验证与开关默认开启
- 目标:跑完交付级验收标准,确认可默认开启
directEdit。 - 范围:补齐
prd.md「验收标准(交付阶段)」逐条证据;开关默认值改为开启。 - 验证:
npm run format:check、npm run test、npm run test:client、npm run test:client:unit、npm run build,加下方手测清单。 - 门禁:交付级验收标准全部勾选;任一条不过则开关保持关闭,任务不进入 Phase 3。
- 回滚点:只改开关默认值,回滚成本为一行。
手测清单
自动化测试覆盖不到的地图行为,每次进入第 8 步都要跑一遍:
- 选中道路 → 出现手柄,浏览器 devtools 中
Map实例未变(拖拽前后同一对象引用)。 - 拖动外缘 → 只有道路面、步行带、车道线、标线、connector 的 source 被替换,底图与未受影响图层无重绘闪烁。
- 连续快速拖拽后松手 → 最终几何与松手位置一致,无回跳(验证乱序丢弃与最终请求)。
- 拖到违反最小车道宽 → 出现阻塞诊断,地图停在最后一个有效预览。
- 手柄落在 junction reserve 内 → 不可拖动并提示进入
JunctionTools。 - 进入
JunctionTools调三项 → 预览完整;应用 → 返回主地图仍显示同一几何;取消 → 状态复原。 - 未处理草稿时尝试切换相邻路口 → 被拒绝并提示先应用或取消。
- 保存 → 重新编译 → 几何不变,约束状态
exact。 - 重新导入同一份 OSM → 约束全部
exact;导入删掉某条道路的 OSM → 相关约束stale,旧 revision 仍可打开复现原结果。 - 两个标签页同时保存 → 后者 409 提示,前者结果保留。
- 关闭
directEdit开关 → 工作台行为与当前 main 一致。
步骤依赖
0 → 1 可并行于 2;2 → 3 → 4 → 5 是硬顺序;6 依赖 1、4、5;7 依赖 6;8 依赖全部。第 1 步失败不阻塞 2-5,只改变第 6 步的输入 adapter 选择。