chore(road-editor): plan direct-edit task tree and add client test infra

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>
This commit is contained in:
2026-08-26 17:33:42 +08:00
parent 7dc7ede2b8
commit 93f09e399e
41 changed files with 2001 additions and 4 deletions

View File

@@ -0,0 +1,107 @@
# 直接操纵道路编辑工作台
## 目标
让道路编译工作台支持类似 Drawtonomy 的直接操纵:用户能在地图上选择生成的道路面并通过有语义的控制柄调整细节,同时保留 OSM 作为输入来源、修改可解释且可审计,并能在重新编译或重新导入后可靠地重放。
## 已确认的事实
- `src/compile/native-road.js` 从 OSM 生成方向道路、车道、道路面、步行带、路口面、标线和连接路径;这些 GeoJSON 是派生输出,不是持久化编辑源。
- 现有 `native-road-overrides/v1` 已支持道路宽度/车道数/步行带开关、路口和车道连接、以及标线样式,且以稳定的 OSM 派生语义 ID 定位目标,并在 OSM 改动后识别失效项。
- 编译器使用 staging 目录原子发布输出;工作台已经具有单例 OpenLayers 地图、命令式图层 source 更新、选择高亮和暂存/保存/重新编译链路。
- Drawtonomy 的公开 SDK 表明其道路由共享 `point``linestring` 和引用左右边界的 `lane` 构成;连接关系和导出几何从该对象图派生。其编辑器核心(手势状态机、选择、历史)不在公开仓库中,不能作为源码依赖。
## 产品要求
1. 编辑目标必须是道路语义对象,而不是直接保存最终 GeoJSON 多边形。
2. 拖拽时必须即时显示预览,并清楚呈现将被同步影响的道路面、车道、标线和路口对象。
3. 每次持久化修改都必须可解释:谁在何处、以什么规则、相对什么输入锚点施加了何种约束。
4. OSM 重新导入或编译器升级后,修改应自动重放、明确报告冲突/失效,绝不能静默改写到错误道路。
5. 用户必须可以撤销/重做未保存编辑,并能查看、禁用或删除已保存编辑。
6. 新架构应复用现有道路参数 overrides、ID 校验、编译和图层更新能力,而不是并行维护第二套地图数据。
7. OSM 道路中心线和拓扑保持只读;直接操纵首期仅编辑由它派生的横断面与路口细节。
8. 首期必须同时验证道路横断面与路口细节的联合编辑道路外缘、步行带、车道分隔以及路口进口、cutback 与转弯角部都属于首批能力。
9. 道路横断面拖拽默认生成以拖拽站点为中心的局部区间,并在区间边界平滑过渡回基线;用户可以用范围控制柄修改影响区间。
10. 拖拽时浏览器立即更新控制柄、辅助线与半透明 ghost在短防抖后调用服务端权威预览服务端使用与正式编译相同的约束求解器返回受影响派生图层。浏览器不维护第二套道路几何算法。
11. 工作台必须支持可复现的存盘 revision冻结一次编译所需的 OSM 输入、区域配置、已有 overrides、直接编辑约束、信号数据和编译身份重新导入 OSM 创建新 revision不覆盖旧 revision。
12. 导入 OSM 自动创建基线 revision用户显式保存命名检查点时创建不可变 revision普通保存和预览只更新活动副本。
13. 路口点击编辑进入专用 `JunctionTools` 工作区,而非在主地图叠加完整路口控制面板;该工作区复用活动 revision 与直接编辑约束,并在操作后尽快重新求解生成道路拓扑和相关派生图层。
14. `JunctionTools` 的草稿从首次拖拽起即显示即时 ghost 和服务端权威几何预览;“应用到工作区”仅合并有效草稿到主工作区,“保存”才持久化,“取消”丢弃本次会话草稿。
15. 一次 `JunctionTools` 会话只编辑一个 node 或 cluster切换相邻路口前必须应用或取消当前草稿。
16. 主地图道路区间编辑仅拥有两个路口之间的内部区间;`JunctionTools` 独占路口保留区内的进口、cutback 与角部约束。求解器保证两侧连续,并以显式 junction approach 约束优先。
17. 首个 `JunctionTools` 交付只验证单一普通路口的进口宽度、cutback 和单个角部圆角;必须展示完整拟合预览并能应用回主工作区。
18. 约束 kind 枚举必须唯一且与首期范围一一对应;车道分隔必须有自己的约束 kind不得借用外缘偏移表达。
19. 预览必须有明确时序保证:乱序响应不得覆盖较新预览;拖拽防抖与 `pointerup` 最终请求分离;超出延迟预算时降级提示而不是几何闪烁。
20. 持久化约束只使用米与归一化 station坐标与单位换算的分层职责必须固定客户端不得用投影坐标差充当米。
21. 区域配置(含 `nativeRoad.junctionTemplates`)必须随活动副本与 revision 冻结为快照,编译与预览只读快照;否则 revision 不可复现。
22. 同机多标签页并发保存必须被显式挡住(版本前置条件加冲突提示),不得静默互相覆盖。多人协作仍延后。
23. revision 不自动删除OSM 副本按内容寻址避免重复;派生产物缓存可清理且清理不影响可复现性。
24. 编译器几何版本变化时,约束既不自动失效也不自动改值,必须经用户显式确认重放。
25. 编辑能力只进 React 工作台;遗留 vanilla 客户端不同步、不在本任务内删除。
26. 客户端纯逻辑必须可自动化测试;地图交互行为以固化在 `implement.md` 的手测清单覆盖。
## 需要完成的研究与设计
- 梳理当前模型、输出 feature 属性和现有 overrides 可直接复用的锚点。
- 明确 Drawtonomy 的公开对象图、共享几何和派生原则,以及它与 OSM 编译流程不相同的边界。
- 比较参数反推、语义几何约束、局部补丁几何三种数据模型,并给出持久化、重放、冲突、撤销和迁移策略。
- 在确认编辑边界后形成交互模型、编译边界、API 合约、版本化 schema 和分阶段实施计划。
- 在实现前验证 `ol-ext` 的 Transform interaction 能否仅操作临时代理 feature并与现有 `ol@10.10.0`、React 生命周期和局部预览稳定协作;验证结果决定是否纳入正式依赖。
- `ol-ext` 验证必须是有时间上限、可独立删除的探针;失败仅替换输入 adapter不得改变约束文档、求解器或编译架构。
- `ol-ext` 探针限时一个工作日;必须同时满足“无地图重建、无基线 source 写入、拖拽稳定”,否则停止排障并采用 OpenLayers 原生 proxy + 小型语义 adapter。
## 任务地图
本任务是父任务:持有需求集、`design.md` 权威合约、跨子任务验收标准与最终集成验证。实现落在四个子任务,顺序依赖写在各自 `prd.md` / `implement.md`,不由树结构隐含。
| 子任务 | 交付物 | 对应父 `implement.md` 步骤 | 前置 |
| --- | --- | --- | --- |
| `08-26-direct-edit-documents` | v2 文档、活动副本、revision、area config 快照、测试基础设施 | 0、2、3 | 无 |
| `08-26-direct-edit-solver-api` | `resolveDirectEditConstraints` 与预览/保存/revision/rebase API | 4、5 | documents |
| `08-26-direct-edit-map-editor` | `ol-ext` 探针与主地图道路区间编辑 | 1、6 | solver-api 第 2 步manifest |
| `08-26-direct-edit-junction-tools` | 单一普通路口的进口、cutback、单角部圆角 | 7 | solver-api、map-editor |
父任务自留第 8 步:跑完交付级验收标准并把 `directEdit` 开关默认开启。
## 延后项
- 不在当前前置架构阶段设计或实现高级复合路口模板编排、多个路口的联合会话、控制设施逐个手工布置、协作合并或全面的拓扑创作工具。
## 首个交付范围
- 版本化直接编辑文档、活动工作副本和命名 revision 检查点。
- 限时的 `ol-ext` 代理 feature 验证及原生 OpenLayers adapter 回退。
- 服务端权威预览 API 与客户端即时 ghost。
- 主地图的道路内部区间:外缘、步行带、车道分隔和范围控制。
- 最小 `JunctionTools`单一普通路口的进口、cutback、单角部圆角、预览/应用/取消。
- 约束重放的精确匹配、待确认和失效状态;旧 revision 永不被重导入覆盖。
## 验收标准(规划阶段)
- [x] 已提供当前能力与可复用边界的证据清单。
- [x] 已区分 Drawtonomy 的公开事实、可迁移原则和不可验证的编辑器内部实现。
- [x] 已确定持久化编辑数据不是最终 GeoJSON并有版本化、可重放、可失效诊断的数据方案。
- [x] 已明确拖拽预览、应用、保存、重新编译、撤销/重做、OSM 重导入和 revision 检查点的行为。
- [x] 已定义 `ol-ext` 限时探针、原生 OpenLayers 回退与不改变数据合同的止损规则。
- [x] 已定义首个 Road editor 与最小 JunctionTools 的范围、所有权和延后项。
- [x] 约束 kind 枚举唯一,且覆盖首期全部 6 项能力(含车道分隔)。
- [x] handle manifest、坐标分层、预览时序、area config 快照、单写者保护、留存策略均已在 `design.md` 定义。
- [x] 已明确遗留客户端边界与客户端测试基础设施的决定。
- [x] 已形成 `design.md``implement.md`,供用户审阅后再开始实现。
## 验收标准(交付阶段)
每条都必须可执行验证,不接受"看起来对了"。
- [ ] 拖动道路外缘 → 保存 → 重新编译,道路面、步行带、车道线、标线与 connector 一致更新,约束状态为 `exact`
- [ ] 车道分隔手柄可独立调整某条分隔线,且不等价于外缘偏移的副作用。
- [ ] 落在 junction reserve 内的道路手柄不可拖动,并给出引导进入 `JunctionTools` 的原因。
- [ ] `JunctionTools` 单路口会话可调进口宽度、cutback 与一个角部圆角,预览包含全部受影响派生对象;应用后主地图继续显示同一预览,取消后回到进入前状态。
- [ ] 违反最小车道宽 2.4m 或造成路口面自交的草稿返回阻塞性诊断,且不替换最后一个有效预览。
- [ ] 快速连续拖拽时乱序预览响应不会覆盖较新结果(可通过注入延迟的测试复现)。
- [ ] 同一 OSM 重新导入后,约束按 `exact` / `pending` / `conflicted` / `stale` 分类报告;旧 revision 仍可打开并复现原结果。
- [ ] 修改 area config 后,旧 revision 的编译结果不变(证明快照生效)。
- [ ] 两个标签页并发保存时,后者收到 409 且不覆盖前者。
- [ ] 未保存编辑可撤销/重做;已保存编辑的撤销以反向操作或禁用约束体现,历史不被重写。
- [ ] 拖拽与选择过程中 OpenLayers `Map` 未被重建,只有受影响 source 被替换。
- [ ] `npm run format:check``npm run test``npm run test:client`、新增客户端单元测试与 `npm run build` 全绿。