diff --git a/.trellis/tasks/08-28-crosswalk-element-editor/check.jsonl b/.trellis/tasks/08-28-crosswalk-element-editor/check.jsonl new file mode 100644 index 0000000..6fbd16f --- /dev/null +++ b/.trellis/tasks/08-28-crosswalk-element-editor/check.jsonl @@ -0,0 +1,3 @@ +{"file":".trellis/spec/backend/direct-edit-api.md","reason":"检查 preview 零写入、非空约束改变正式编译输出、保存与 rebase 合同。"} +{"file":".trellis/spec/frontend/direct-edit-client.md","reason":"检查 baseline 不被元素编辑代码写入、控件由 manifest 驱动且预览时序正确。"} +{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"核对 feature -> element -> manifest -> constraint 的完整读写链路。"} diff --git a/.trellis/tasks/08-28-crosswalk-element-editor/design.md b/.trellis/tasks/08-28-crosswalk-element-editor/design.md new file mode 100644 index 0000000..5df75a3 --- /dev/null +++ b/.trellis/tasks/08-28-crosswalk-element-editor/design.md @@ -0,0 +1,59 @@ +# 斑马线元素级编辑器设计 + +## 架构边界 + +本实验扩展现有直接编辑链路,而非建立画布对象存储: + +```text +crosswalk feature 点击 + -> elementId + -> element manifest (anchor + parameters + legal controls) + -> native-road-edits/v2 constraint + -> resolveDirectEditConstraints + -> compileControlMarkings + -> preview / save / recompile +``` + +`elementId` 是选择和分组标识;持久化仍使用稳定语义 anchor 上的 constraint。它不能是 GeoJSON 数组下标、屏幕坐标或临时 UUID。 + +## 选择合同 + +斑马线的每一条 stripe feature 必须包含 `native_id`、`element_id` 与可回链的 crossing / approach 身份。选择适配层只读取 `element_id`,再向服务器取得选中元素的权威 manifest。客户端不会据条带数量、空间距离或渲染顺序自行聚类。 + +元素 manifest 至少包含: + +```ts +interface EditableElement { + elementId: string + kind: 'crosswalk' + anchor: { type: 'junction-approach'; nodeId: string; segmentId: string } + featureIds: string[] + controls: Array<{ + kind: 'position' | 'length' | 'width' + value: { current: number; min: number; max: number; unit: 'meter' } + axisAzimuth?: number + }> + fields: Array<{ key: string; value: number; min: number; max: number; unit: 'meter' | 'count' }> +} +``` + +第一阶段只发布 `position` 及其现有 `junction-crosswalk-inset` 回写规则。第二阶段再加入尺寸和条带参数,避免在实验开始前规定尚未验证的通用元素协议。 + +## 编辑与预览 + +- 选择由 `MapCanvas` 的 feature 事件产生,但选择状态由 App/编辑会话持有,不能写入 baseline source。 +- 元素控件写入的是 manifest 指定的 constraint kind/value;表单复用同一写入器。 +- 选中框和控件只是 `editHandles` / `editGhost` 层的临时 UI;权威预览仍由服务端返回,baseline source 不被客户端改写。 +- 空白点击会清除元素选择;道路选择可继续决定道路级 handle manifest。斑马线元素选中与道路选择可并存,但两组控件不得重叠或竞争同一 pointer 手势。 + +## 数据演进 + +第一阶段不新增 persisted schema:位置继续使用已有 `junction-crosswalk-inset`。第二阶段如果确实需要尺寸参数,新增一个专属 kind 或扩展同一 kind 的严格 schema 校验;具体选择以第一阶段运行结果为准,不预先固化。 + +缺省参数必须字节级保持现有斑马线输出。参数越界、条带间互相重叠或越过可用进口范围均返回 blocking diagnostic,不在客户端静默钳制真实约束。 + +## 兼容与回滚 + +- 无新 constraint 时,编译输出与当前版本一致。 +- `directEdit` 关闭时不请求 element manifest、不安装元素选择交互、不产生额外图层。 +- 第一阶段每个步骤独立提交;回滚元素 UI 或 manifest 不影响既有道路级控制。 diff --git a/.trellis/tasks/08-28-crosswalk-element-editor/implement.jsonl b/.trellis/tasks/08-28-crosswalk-element-editor/implement.jsonl new file mode 100644 index 0000000..a2120a5 --- /dev/null +++ b/.trellis/tasks/08-28-crosswalk-element-editor/implement.jsonl @@ -0,0 +1,4 @@ +{"file":".trellis/spec/backend/direct-edit-api.md","reason":"元素 manifest、preview 和持久化必须继续满足直接编辑的服务端合同。"} +{"file":".trellis/spec/frontend/direct-edit-client.md","reason":"元素选择、临时控件与表单必须保持 handle/ghost/preview 图层所有权。"} +{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"feature 属性、element manifest、constraint schema 与客户端选择状态跨越编译器、API 和 UI。"} +{"file":".trellis/tasks/08-26-direct-manipulation-road-editor/design.md","reason":"继承 v2 语义约束、坐标分层、预览和 revision 的权威合同。"} diff --git a/.trellis/tasks/08-28-crosswalk-element-editor/implement.md b/.trellis/tasks/08-28-crosswalk-element-editor/implement.md new file mode 100644 index 0000000..ccb08e0 --- /dev/null +++ b/.trellis/tasks/08-28-crosswalk-element-editor/implement.md @@ -0,0 +1,49 @@ +# 实施计划 + +## 0. 建立事实与测试夹具 + +- 追踪 `compileControlMarkings()` 的每条 stripe 属性、`native_id` 构成和从地图点击到 `onFeature` 的路径。 +- 添加失败优先的测试:同组 stripe 需要稳定共享 `elementId`,不同 crossing 不得共享;空编辑输出与基线一致。 +- 门禁:能证明一个具体 feature 无歧义回链到一个 junction approach 和 crossing。 + +## 1. 第一阶段:元素 manifest 与选择 + +- 在编译器为 crosswalk stripe 写稳定的元素标识和回链属性。 +- 在 direct-edit 服务响应中增量发布按 selection/element 获取的元素 manifest,不扩大现有道路 handle 的职责。 +- 客户端建立 `ElementSelection` 纯状态与 feature 点击适配;点击 stripe 选择整组、空白清除。 +- 验证:编译器单元测试、API 测试、客户端 selection 单元测试。 + +## 2. 第一阶段:位置编辑闭环 + +- 用 element manifest 的 position control 渲染元素级控件和选中外框;安装时不得影响道路 handle hit-test。 +- 从位置手势和表单走同一个 draft builder,写入 `junction-crosswalk-inset`。 +- 验证:拖拽与表单值生成同形约束;预览/保存/重载一致;`exact` 状态和撤销/重做覆盖。 +- 门禁:一次非空编辑可在正式重编译结果中观察到斑马线位置变化。失败即停止,不能只交付 UI。 + +## 3. 复盘门禁 + +- 手测选中、取消、道路级手柄共存、拖拽钳位、表单验证、保存重载。 +- 只有在第一阶段验收全部通过后,才能为尺寸/外观参数做持久化 schema 决策。 + +## 4. 第二阶段:尺寸与外观 + +- 依据第一阶段证据设计并验证长度、宽度、条带数、间距的约束 schema。 +- 编译器读取参数、返回 blocking diagnostics;元素 manifest 表达对应控件/字段。 +- 实现与表单字段完全一致的尺寸控件,补齐组合参数、持久化和 rebase 测试。 + +## 验证命令 + +```sh +npm run format:check +npm run test +npm run test:client +npm run test:client:unit +npm run build +``` + +## 风险文件 + +- `src/compile/native-road.js`:斑马线生成与默认输出兼容性。 +- `src/compile/direct-edit-solver.js` / `native-road-edits.js`:约束求解、schema 和 manifest 必须同步。 +- `workbench/server.js`:element manifest 查询与 preview 不得写入磁盘。 +- `workbench/client/src/components/MapCanvas.tsx`、`App.tsx`、`edit/`:选择、临时控件和表单不得污染 baseline layer。 diff --git a/.trellis/tasks/08-28-crosswalk-element-editor/prd.md b/.trellis/tasks/08-28-crosswalk-element-editor/prd.md new file mode 100644 index 0000000..ece2d13 --- /dev/null +++ b/.trellis/tasks/08-28-crosswalk-element-editor/prd.md @@ -0,0 +1,63 @@ +# 斑马线元素级编辑器实验 + +## 目标 + +验证“元素级编辑器”能取代只显示固定道路参数手柄的交互:用户点击一组斑马线的任一条带后,整组进入选中状态,地图显示该元素的编辑控件,参数表单与地图操作都能预览、保存和撤销同一份结构化编辑。 + +斑马线是实验对象,实验通过后才抽象和推广到停止线、箭头、信号灯等元素。 + +## 已确认的事实 + +- 当前 `junction-crosswalk-inset` 只改变斑马线相对路口的位置;控制手柄属于被选道路的进口,而非具体斑马线。 +- `compileControlMarkings()` 为一组斑马线生成多条 `crosswalk` GeoJSON feature;每条 feature 的属性目前不足以作为稳定的组级编辑目标。 +- 浏览器已有 `EditSession`、服务端权威 preview、保存、重编译与撤销链路。编辑数据必须保持语义约束,不得保存派生 GeoJSON 或屏幕坐标。 +- 当前普通道路级手柄仍服务于横断面参数,不应与元素级选择共用或互相覆盖。 + +## 范围 + +### 第一阶段:元素选择与位置闭环 + +1. 点击任意一条斑马线条带,选中整组斑马线;点击空白处或另一道路元素清除或切换选择。 +2. 后端为每组斑马线发布稳定 `elementId`、元素类型、语义锚点、可编辑参数与受影响 feature ID;客户端不得由 feature 数组位置猜测分组。 +3. 选中后显示轻量的元素级编辑状态,至少有位置控制;移动只允许沿该进口定义的合法轴和范围,钳位来自服务端 manifest。 +4. 位置控制和参数表单共同更新既有 `junction-crosswalk-inset` 约束,并复用 preview、保存、撤销与重编译链路。 +5. 表单显示当前值、单位、合法范围和阻塞性诊断;修改后更新地图预览,保存后重新编译仍为 `exact`。 + +### 第二阶段:尺寸与外观闭环(本任务内设计、第一阶段通过后实施) + +1. 选中框提供与斑马线语义相符的长度/宽度调整,不写自由像素缩放。 +2. 表单可修改斑马线长度、宽度、条带数量和条带间距;地图控件与表单写同一结构化元素参数。 +3. 编译器消费这些参数生成整组条带,并对不合法组合返回阻塞诊断。 + +## 明确不做 + +- 不实现通用的任意旋转、自由二维移动、任意缩放或把 transform 矩阵持久化。 +- 不编辑多组斑马线,不跨进口/跨路口拖放,不改变 OSM 中心线或路口拓扑。 +- 不在本任务中推广停止线、箭头或信号灯;不提前建立“所有元素”的大而全抽象。 +- 不移除或改变道路横断面、路口 shape 的现有手柄和所有权。 + +## 验收标准 + +- [ ] 点击一条条带后,整组条带显示为同一选中元素;切换到另一组或空白处时状态正确更新。 +- [ ] `elementId` 在同一输入、重编译、保存和重新加载后稳定;同组条带共享一个 ID,不同斑马线不共享。 +- [ ] 移动控件与表单修改位置时,预览只更新该元素及其声明的受影响层;无效输入保留最后一个有效预览并显示诊断。 +- [ ] 保存、刷新、重新编译后位置不回退,相关约束状态为 `exact`;未保存编辑可以撤销/重做。 +- [ ] 第二阶段完成后,长度/宽度/条带数量/间距能从表单和地图控件一致地更新,非法尺寸或间距被拒绝而非静默修复。 +- [ ] 现有道路级手柄仍可独立工作,且未选中斑马线时不显示元素编辑 UI。 +- [ ] `npm run format:check`、`npm run test`、`npm run test:client`、`npm run test:client:unit`、`npm run build` 通过。 + +## 风险与门槛 + +- 第一阶段必须先证明 feature -> element -> semantic anchor 的稳定回链,以及非空约束确实改变正式编译输出;两者任一失败时停止,不进入尺寸和外观参数。 +- 选中框是交互反馈,不能成为第二个几何模型;所有位置、范围和尺寸的权威值由后端 element manifest 给出。 +- 当前地图 feature 的属性可能缺少足以识别 crossing 的稳定字段。需要先通过编译器补充,不可在客户端按经纬度近似聚类。 + +## Acceptance Criteria + +- [ ] TBD + +## Notes + +- Keep `prd.md` focused on requirements, constraints, and acceptance criteria. +- Lightweight tasks can remain PRD-only. +- For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start`. diff --git a/.trellis/tasks/08-28-crosswalk-element-editor/task.json b/.trellis/tasks/08-28-crosswalk-element-editor/task.json new file mode 100644 index 0000000..0f38772 --- /dev/null +++ b/.trellis/tasks/08-28-crosswalk-element-editor/task.json @@ -0,0 +1,26 @@ +{ + "id": "crosswalk-element-editor", + "name": "crosswalk-element-editor", + "title": "斑马线元素级编辑器实验", + "description": "", + "status": "planning", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "dingkang", + "assignee": "dingkang", + "createdAt": "2026-08-28", + "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": {} +} \ No newline at end of file