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:
5
.trellis/tasks/08-26-direct-edit-map-editor/check.jsonl
Normal file
5
.trellis/tasks/08-26-direct-edit-map-editor/check.jsonl
Normal file
@@ -0,0 +1,5 @@
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/design.md", "reason": "核对坐标分层(禁止用 3857 坐标差当米)、预览时序仲裁位置与开关关闭时的零残留要求"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/canvas-integration.md", "reason": "核对基线图层保持只读、手柄只带 handleId、未把地理坐标直接写成道路 polygon"}
|
||||
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "检查客户端是否出现第二份约束模型或对 manifest 字段的局部 cast"}
|
||||
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "检查是否重复实现图层更新、选择高亮或米制换算"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/current-system.md", "reason": "核对 Map 单例未被重建、既有图层可见性与选择行为未回归"}
|
||||
22
.trellis/tasks/08-26-direct-edit-map-editor/design.md
Normal file
22
.trellis/tasks/08-26-direct-edit-map-editor/design.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 设计
|
||||
|
||||
技术合约不在本文重复定义。权威定义见父任务 `.trellis/tasks/08-26-direct-manipulation-road-editor/design.md` 的以下小节:
|
||||
|
||||
- 「Handle manifest」— 客户端读取的字段与 `reserves` 禁入区语义。
|
||||
- 「坐标与单位分层」— 客户端渲染用 EPSG:3857,指针位移换米必须先回 4326 再用球面距离。
|
||||
- 「预览时序与延迟预算」— 80ms 防抖、`pointerup` 最终请求、`previewSeq` 乱序丢弃、`degraded` 处理。
|
||||
- 「交互与预览」「编辑所有权」— 只读基线图层、三个独立 source、道路区间与 junction reserve 的边界。
|
||||
- 「客户端边界」— 遗留 `app.js` 不在范围、`vitest` 覆盖范围。
|
||||
- 「上线与回滚形态」— `directEdit` 开关默认关闭时的行为。
|
||||
|
||||
输入 adapter 的候选、探针退出条件与回退优先级见父任务 `research/canvas-integration.md`。
|
||||
|
||||
## 本子任务的局部决定
|
||||
|
||||
- 三个 source 严格分离:`editHandles`(手柄)、`editGhost`(客户端即时轮廓与辅助线)、`editPreview`(服务端权威几何)。基线图层始终只读,绝不写入。
|
||||
- 手柄 feature 的 properties 只带 `handleId`,语义一律回 manifest 查。这样客户端没有第二份约束模型。
|
||||
- ghost 只画手柄、辅助线与半透明预估轮廓,不承担权威几何。服务端预览到达后替换 `editPreview`,ghost 随即淡出。
|
||||
- `EditSession` 是纯逻辑对象,不持有 OpenLayers 引用:命令栈、undo/redo、`previewSeq` 仲裁都可在 node 环境下单测。OL 只作为它的输入输出适配层。
|
||||
- `previewSeq` 仲裁放在 `EditSession` 而非请求层:请求层只负责发与取消,丢弃决策要可测。
|
||||
- 探针独立成可删除目录,不进 `MapCanvas`。探针结论只影响"手柄命中与拖拽由谁实现",不影响上面任何一条。
|
||||
- 开关关闭时不注册 interaction、不请求 manifest、不创建三个 source,确保零残留开销。
|
||||
@@ -0,0 +1,6 @@
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/design.md", "reason": "父任务权威合约:handle manifest 字段、坐标分层、预览时序、编辑所有权、客户端边界、directEdit 开关"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/canvas-integration.md", "reason": "ol-ext 探针的退出条件、回退优先级,以及为何不引入第二个 Canvas/视口"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/joint-solver.md", "reason": "EditSession 的交互序列与浏览器 ghost 的职责边界"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/current-system.md", "reason": "现有单例 Map、source registry 与选择高亮的可复用点"}
|
||||
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "manifest 字段从服务端到客户端类型的流转,避免局部 cast 与重复状态"}
|
||||
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "图层与 source 更新必须复用现有 layers.ts 模式,米制换算不得重写"}
|
||||
49
.trellis/tasks/08-26-direct-edit-map-editor/implement.md
Normal file
49
.trellis/tasks/08-26-direct-edit-map-editor/implement.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 实施计划
|
||||
|
||||
对应父任务 `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 步的命中与拖拽实现方式。
|
||||
42
.trellis/tasks/08-26-direct-edit-map-editor/prd.md
Normal file
42
.trellis/tasks/08-26-direct-edit-map-editor/prd.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 主地图道路区间编辑器
|
||||
|
||||
父任务:`.trellis/tasks/08-26-direct-manipulation-road-editor`。需求来源与权威合约在父任务的 `prd.md` / `design.md`。
|
||||
|
||||
## 目标
|
||||
|
||||
在主地图上实现道路内部区间的直接操纵:外缘、步行带、车道分隔与区间范围可拖,拖拽即时有 ghost,服务端返回权威预览,改动可撤销、可保存。
|
||||
|
||||
## 顺序依赖
|
||||
|
||||
前置:`direct-edit-solver-api` 的第 2 步(handle manifest)交付后即可开工;完整验收需要其预览与保存端点全部就绪。
|
||||
后继:`direct-edit-junction-tools` 复用本任务的 `EditSession`、ghost/preview 图层与命令栈。
|
||||
|
||||
## 范围
|
||||
|
||||
- `ol-ext` Transform 的限时隔离探针,以及不通过时的原生 OpenLayers 回退。
|
||||
- selection → handle manifest 的读取与渲染;handles / ghost / preview 三个独立 source。
|
||||
- 四类可拖能力:外缘偏移、步行带宽度、车道分隔位置、区间范围控制。
|
||||
- `EditSession`:命令栈、undo/redo、应用/保存/取消。
|
||||
- 80ms 防抖预览、`pointerup` 强制最终请求、`previewSeq` 乱序丢弃、`AbortController` 取消在途请求。
|
||||
- 无效草稿的诊断反馈,保留最后一个有效预览。
|
||||
- `directEdit` 开关,默认关闭。
|
||||
|
||||
## 不做
|
||||
|
||||
- 路口 reserve 内的任何编辑(属 `direct-edit-junction-tools`)。
|
||||
- 遗留 `workbench/client/app.js` 的同步或删除。
|
||||
- OSM 中心线与拓扑的编辑。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] 选中道路后出现手柄,拖拽前后 OpenLayers `Map` 为同一实例,未被重建。
|
||||
- [ ] 拖动外缘 → 道路面、步行带、车道线、标线、connector 一致更新,只有受影响 source 被替换。
|
||||
- [ ] 车道分隔手柄可独立调整某条分隔线,效果不等价于外缘偏移的副作用。
|
||||
- [ ] 区间范围手柄可修改影响区间,两端平滑过渡回基线。
|
||||
- [ ] 落在 junction reserve 内的手柄不可拖动,并提示进入 `JunctionTools`。
|
||||
- [ ] 连续快速拖拽后松手,最终几何与松手位置一致,无回跳。
|
||||
- [ ] 违反不变量的草稿显示阻塞诊断,地图停在最后一个有效预览。
|
||||
- [ ] 未保存编辑可撤销/重做;保存后重新编译几何不变,约束状态 `exact`。
|
||||
- [ ] 客户端单元测试覆盖:命令栈与 undo/redo、`previewSeq` 乱序丢弃、handle 事件到约束值的投影、米制换算在不同纬度的正确性。
|
||||
- [ ] 关闭 `directEdit` 开关后,工作台行为与当前 main 一致。
|
||||
- [ ] `npm run format:check`、`npm run test`、`npm run test:client`、`npm run test:client:unit`、`npm run build` 全绿。
|
||||
26
.trellis/tasks/08-26-direct-edit-map-editor/task.json
Normal file
26
.trellis/tasks/08-26-direct-edit-map-editor/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "direct-edit-map-editor",
|
||||
"name": "direct-edit-map-editor",
|
||||
"title": "主地图道路区间编辑器",
|
||||
"description": "ol-ext 限时探针与主地图外缘/步行带/车道分隔/区间范围拖拽",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-26",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-26-direct-manipulation-road-editor",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user