# 实施计划 对应父任务 `implement.md` 的第 1、6 步。每步一个提交,门禁不过就停。 前置:`direct-edit-solver-api` 第 2 步(handle manifest)已可用。完整验收需其预览与保存端点就绪。 ## 1. `ol-ext` 限时探针 - 目标:只回答一个问题——能否复用通用 handle 的命中、pointer 生命周期与视觉反馈。 - 范围:隔离探针目录,不修改生产基线图层,不进依赖清单直到通过。 - 验证:手动跑探针页,确认 OL `Map` 未重建、proxy 拖拽稳定、基线 source 未被写入、事件能转成 draft 值。 - 门禁:三条同时成立才把 `ol-ext` 固定为依赖。超过一个工作日或任一条不成立,立刻停止排障。 - 回滚点:删除探针目录,改用原生 OL `Snap` + 小型 `PointerInteraction` adapter。数据合约不变,本步失败只换输入层。 ## 2. `EditSession` 纯逻辑 - 目标:先把可单测的部分做完,与地图解耦。 - 范围:命令栈与 undo/redo;`previewSeq` 仲裁;handle 事件到约束值的投影;指针位移到米的换算。不含任何 OpenLayers 引用。 - 验证:`npm run test:client:unit` —— 命令栈压入/撤销/重做序列正确;乱序响应(后发先到)被丢弃且不覆盖较新预览;投影在四种 kind 上给出预期约束值;同一像素位移在低纬与高纬得到一致的米值(纬度缩放正确)。 - 门禁:四类纯逻辑全部有测试;`EditSession` 不 import `ol`。 - 回滚点:纯新增模块,无调用方,revert 无影响。 ## 3. 手柄渲染与开关 - 目标:选中道路后能看到正确的手柄,且禁入区可见可解释。 - 范围:`directEdit` 开关(默认关闭);selection → manifest 读取;`editHandles` source 渲染;reserve 内手柄置灰并带原因提示。只读不可拖。 - 验证:`npm run test:client`、`npm run build`;手测——选中道路出现手柄,拖拽前后 `Map` 为同一实例;reserve 内手柄置灰并提示进入 `JunctionTools`;关闭开关后无手柄、无 manifest 请求。 - 门禁:`Map` 未被重建;基线 source 未被写入。 - 回滚点:开关关闭即止损。 ## 4. 拖拽、ghost 与预览 - 目标:四类能力真正可拖,预览权威且不闪烁。 - 范围:外缘偏移、步行带宽度、车道分隔位置、区间范围控制;`editGhost` 即时反馈;80ms 防抖与 `pointerup` 最终请求;`AbortController` 取消在途请求;`editPreview` 替换;无效草稿诊断与保留最后有效预览。 - 验证:`npm run test:client:unit`、`npm run test:client`、`npm run build`;手测——拖动外缘时道路面/步行带/车道线/标线/connector 一致更新且只有受影响 source 被替换;车道分隔可独立调整;区间范围手柄改变影响区间且两端平滑;连续快速拖拽松手后无回跳;违反不变量时停在最后有效预览并显示诊断。 - 门禁:只有受影响 source 被替换,无全图层重建;无回跳。 - 回滚点:按能力分批提交,可单独回退某一类手柄。 ## 5. 应用、保存与撤销 - 目标:闭合编辑生命周期。 - 范围:应用/保存/取消;undo/redo 接到命令栈;保存走 `expectedDocumentVersion`;409 冲突提示。 - 验证:`npm run test:client:unit`(保存冲突分支)、`npm run test:client`、`npm run build`;手测——未保存编辑可撤销/重做;保存后重新编译几何不变且状态 `exact`;两标签页同时保存后者收到 409 提示且前者结果保留。 - 门禁:撤销不重写已保存历史;409 有明确用户提示而非静默失败。 - 回滚点:revert 后回到第 4 步的预览-only 状态。 ## 步骤依赖 1 与 2 可并行;3 依赖 1 的结论与 2;4 依赖 3;5 依赖 4。第 1 步失败不阻塞 2-5,只改变第 3、4 步的命中与拖拽实现方式。