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-solver-api/check.jsonl
Normal file
5
.trellis/tasks/08-26-direct-edit-solver-api/check.jsonl
Normal file
@@ -0,0 +1,5 @@
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/design.md", "reason": "逐字段核对 handle manifest、API 合约、409 前置条件与 degraded 标记是否与权威合约一致"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/joint-solver.md", "reason": "逐条核对约束不变量:最小车道宽 2.4m、外缘不交叉、路口连续、路口面不自交、connector 包含性"}
|
||||
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "检查是否出现第二套米制换算或重复的 overrides 应用逻辑"}
|
||||
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "检查约束 kind 与 manifest 字段在编译器、服务端响应之间是否各写一套"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/current-system.md", "reason": "核对既有 /api/overrides、/api/compile、/api/state 行为与 baseline 输出未回归"}
|
||||
22
.trellis/tasks/08-26-direct-edit-solver-api/design.md
Normal file
22
.trellis/tasks/08-26-direct-edit-solver-api/design.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 设计
|
||||
|
||||
技术合约不在本文重复定义。权威定义见父任务 `.trellis/tasks/08-26-direct-manipulation-road-editor/design.md` 的以下小节:
|
||||
|
||||
- 「架构边界」— `resolveDirectEditConstraints` 在 `compileGeometry()` 之前的插入位置与数据流。
|
||||
- 「约束模型」— 6 个 kind 与各自的锚点、value、单位。
|
||||
- 「Handle manifest」— `HandleManifest` / `EditHandle` / `JunctionReserve` 字段。
|
||||
- 「坐标与单位分层」— 服务端在局部米制框架求解,传输一律 EPSG:4326 加方位角。
|
||||
- 「预览时序与延迟预算」— `previewSeq` 回显、`degraded` 标记、p95 ≤ 300ms。
|
||||
- 「编辑所有权」「不变量」— 两域划分与必须验证的几何不变量。
|
||||
- 「API 合约」— 5 个新端点、409 前置条件、既有端点的增量字段。
|
||||
- 「编译器几何版本变更」— `recheck` 策略。
|
||||
|
||||
## 本子任务的局部决定
|
||||
|
||||
- 求解器是纯函数,放在 `src/compile/` 下独立模块,不依赖 `fs` 与 `http`:预览、正式编译与 CLI 导出共用同一实现,这是"浏览器不维护第二套几何算法"的落点。
|
||||
- 米制换算全部走 `src/geometry/lane-geometry.js` 现有的 `metersAt()` / `haversineMeters()` / `projectedDistanceAlong()` / `lateralOffsetFrom()`。禁止新增换算函数;缺能力就扩展该模块。
|
||||
- handle manifest 与 diagnostics 是求解器的返回值,不是编译产物文件:预览不写盘,正式编译才落 GeoJSON。
|
||||
- `axisAzimuth` 由服务端算好,客户端只做投影。这样"哪个方向可拖"是语义决定而非 UI 猜测。
|
||||
- 重放匹配与求解分两个阶段:先把约束解析成 `exact` / `recheck` / `pending` / `conflicted` / `stale`,只有前两态进入求解。这样诊断与几何互不污染。
|
||||
- `POST /api/edit-preview` 复用现有请求体读取与 `sendJson` 工具,不引入新的 HTTP 框架。
|
||||
- 端点顺序:先 `GET /api/edit-state` 与 `POST /api/edit-preview`(只读/无写入,风险最低),再 `POST /api/edits` 与 revision 写入端点。
|
||||
@@ -0,0 +1,7 @@
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/design.md", "reason": "父任务权威合约:约束模型、handle manifest 字段、坐标分层、预览时序、不变量、API 合约"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/joint-solver.md", "reason": "求解阶段插入点、图层依赖链顺序与约束不变量的完整来源"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/current-system.md", "reason": "compileGeometry 现有输入输出、feature 回链字段与 v1 overrides 复用点"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/data-model-options.md", "reason": "分层求解规则与重放匹配策略的推导过程"}
|
||||
{"file": ".trellis/tasks/08-26-direct-manipulation-road-editor/research/junction-tools.md", "reason": "junction reserve 所有权划分与 approach 约束优先级的依据"}
|
||||
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "米制换算必须复用 src/geometry/lane-geometry.js,禁止新增第二套换算函数"}
|
||||
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "新增 API 载荷与 manifest 字段跨编译器/服务端/客户端三层"}
|
||||
57
.trellis/tasks/08-26-direct-edit-solver-api/implement.md
Normal file
57
.trellis/tasks/08-26-direct-edit-solver-api/implement.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 实施计划
|
||||
|
||||
对应父任务 `implement.md` 的第 4、5 步。每步一个提交,门禁不过就停。
|
||||
|
||||
前置:`direct-edit-documents` 已交付(v2 文档 schema、活动副本、area config 快照可读)。
|
||||
|
||||
## 1. 求解器骨架与恒等性
|
||||
|
||||
- 目标:插入 `resolveDirectEditConstraints` 阶段,且在无 v2 约束时是恒等变换。
|
||||
- 范围:`src/compile/` 新增纯函数模块;在 `compileGeometry()` 前接入;输出 editable profiles、junction plans、handle manifest、diagnostics 四件套的空实现。
|
||||
- 验证:`npm run test`,与 `direct-edit-documents` 建立的快照逐字节比对无差异。
|
||||
- 门禁:无 v2 约束时编译输出与快照完全一致。这条不过不许继续。快照必须是 `direct-edit-documents` 从当前 main 重新生成的那份,不是仓库原有的过期 `test/baseline/*.json`。
|
||||
- 回滚点:恒等变换,可安全单独 revert。
|
||||
|
||||
## 2. junction reserve 与 handle manifest
|
||||
|
||||
- 目标:先产出"能拖什么、不能拖什么",为客户端解锁并行开发。
|
||||
- 范围:junction reserve 计算;6 个 kind 的 handle 生成,含 `position`、`axisAzimuth`、`value.min/max`、`affects`、`editable` 与 `disabledReason`。
|
||||
- 验证:`npm run test` 新增 fixture——reserve 内的道路 handle 一律 `editable: false` 且带原因;`affects` 覆盖道路面、步行带、车道线、标线与 connector;`axisAzimuth` 与道路法线/切线方向一致。
|
||||
- 门禁:manifest 字段与父任务 `design.md`「Handle manifest」逐字段一致,客户端可据此开工。
|
||||
- 回滚点:纯新增返回值,无调用方时 revert 无影响。
|
||||
|
||||
## 3. 六个 kind 的求解与不变量
|
||||
|
||||
- 目标:约束真正改变几何,且违规时给阻塞性诊断。
|
||||
- 范围:`road-edge-offset`、`road-sidewalk-width`、`road-lane-divider`、`junction-approach-width`、`junction-cutback`、`junction-corner-radius`;interval 默认范围推导(避开两端 reserve);`smoothstep` 过渡;`junction-approach-width` 优先级与两域边界连续。
|
||||
- 验证:`npm run test` 新增 fixture——每个 kind 一条重放用例;最小车道宽 2.4m、左右外缘不交叉、道路/路口连续、路口面不自交、connector 包含性各一条违规用例返回阻塞诊断。
|
||||
- 门禁:违规一律诊断,绝不静默修复或钳制到合法值。
|
||||
- 回滚点:按 kind 分批提交,可单独回退某个 kind。
|
||||
|
||||
## 4. 重放匹配与状态机
|
||||
|
||||
- 目标:OSM 重导入后约束的 5 态分类可靠。
|
||||
- 范围:精确匹配(`roadId` / `segmentId` / `nodeId`)优先;失败才用 `anchorSnapshot` 的 OSM node、距离与切线找候选;编译器版本不一致时全部标 `recheck`。
|
||||
- 验证:`npm run test` 新增 fixture——同一 OSM 全 `exact`;删掉某条道路后相关约束 `stale`;`boundaryIndex` 越界 `stale`;候选歧义或偏差超阈值 `conflicted`;版本变化全 `recheck` 且值未变。
|
||||
- 门禁:`pending` / `conflicted` / `stale` 一律不进入求解,不存在静默模糊应用的路径。
|
||||
- 回滚点:匹配模块独立,revert 后退回仅精确匹配。
|
||||
|
||||
## 5. 只读端点
|
||||
|
||||
- 目标:先上无写入风险的两个端点。
|
||||
- 范围:`GET /api/edit-state`、`POST /api/edit-preview`;`previewSeq` 回显、`degraded` 标记;复用现有请求体读取与 `sendJson`。
|
||||
- 验证:`npm run test` 新增 server integration——预览调用前后工作区目录内容与 mtime 不变;`previewSeq` 原样回显;预览与正式编译对同一约束集给出相同几何;超预算时 `degraded: true` 而非报错。
|
||||
- 门禁:预览零写入,有测试证明。
|
||||
- 回滚点:新增路由,revert 后其余端点不受影响。
|
||||
|
||||
## 6. 写入端点与并发保护
|
||||
|
||||
- 目标:保存、revision 与 rebase 落地,并挡住多标签页覆盖。
|
||||
- 范围:`POST /api/edits`(要求 `expectedDocumentVersion`)、`POST /api/revisions`、`POST /api/revisions/:id/rebase`;`/api/state`、`/api/session`、`/api/import` 增量字段。
|
||||
- 验证:`npm run test` 新增 server integration——版本不匹配返回 409 且不写入;原子写入中途失败不留半份文件;rebase 返回各 status 计数与明细;`/api/import` 成功后返回基线 revision id;既有 `/api/overrides`、`/api/compile`、`/api/state` 字段与行为不变。
|
||||
- 门禁:409 路径与原子失败路径都有测试覆盖;既有端点无回归。
|
||||
- 回滚点:写入端点独立,revert 后回到只读预览。
|
||||
|
||||
## 步骤依赖
|
||||
|
||||
1 → 2 → 3 硬顺序;4 可与 3 并行;5 依赖 2;6 依赖 5 与 `direct-edit-documents`。第 2 步交付后客户端子任务即可并行开工。
|
||||
42
.trellis/tasks/08-26-direct-edit-solver-api/prd.md
Normal file
42
.trellis/tasks/08-26-direct-edit-solver-api/prd.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 约束求解器与编辑 API
|
||||
|
||||
父任务:`.trellis/tasks/08-26-direct-manipulation-road-editor`。需求来源与权威合约在父任务的 `prd.md` / `design.md`。
|
||||
|
||||
## 目标
|
||||
|
||||
把约束解析从几何生成中抽成纯函数边界,并用它同时支撑草稿预览与正式编译;对外提供预览、保存、revision 与 rebase 的服务端能力。本子任务不含任何 UI。
|
||||
|
||||
## 顺序依赖
|
||||
|
||||
前置:`direct-edit-documents`(需要 v2 文档 schema、`documentVersion`、活动副本与 area config 快照读取)。
|
||||
后继:`direct-edit-map-editor` 与 `direct-edit-junction-tools` 依赖本任务的 handle manifest 与预览 API。
|
||||
|
||||
## 范围
|
||||
|
||||
- `resolveDirectEditConstraints` 纯函数:输入基线模型 + v1 覆盖 + v2 约束,输出 editable profiles、junction plans、handle manifest、diagnostics。
|
||||
- 6 个约束 kind 的求解;interval 默认范围推导(避开两端 junction reserve);junction reserve 计算。
|
||||
- `junction-approach-width` 对道路 profile 的优先级,以及两域边界的连续过渡。
|
||||
- 约束重放匹配:精确匹配优先,失败才按 `anchorSnapshot` 找候选,产出 5 态 `ConstraintStatus`。
|
||||
- 端点:`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` 的增量字段。
|
||||
|
||||
## 不做
|
||||
|
||||
- 任何客户端改动、手柄渲染或拖拽交互。
|
||||
- cluster 的高级求解(只保证 cluster 不被当成多个普通路口处理)。
|
||||
- 多人协作的并发合并策略。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] 求解是纯函数:无文件写入、无网络调用,可用单元 fixture 直接驱动。
|
||||
- [ ] 无 v2 约束时编译输出与当前 main 逐字节一致(baseline fixture 比对)。
|
||||
- [ ] 6 个 kind 各有重放 fixture;预览与正式编译对同一约束集给出相同几何。
|
||||
- [ ] 违反最小车道宽 2.4m、左右外缘交叉、路口面自交、connector 越界时返回阻塞性诊断而非静默修复。
|
||||
- [ ] `boundaryIndex` 越界转 `stale`;锚点缺失转 `stale`;候选歧义或偏差超阈值转 `conflicted`。
|
||||
- [ ] 编译器几何版本不一致时约束全部标 `recheck`,不自动改值也不自动失效。
|
||||
- [ ] `POST /api/edit-preview` 调用前后工作区目录内容与 mtime 不变。
|
||||
- [ ] `expectedDocumentVersion` 不匹配返回 409 且不写入;原子写入中途失败不留半份文件。
|
||||
- [ ] rebase 返回各 status 的计数与逐条明细。
|
||||
- [ ] 既有 `/api/overrides`、`/api/compile`、`/api/state` 的字段与行为不变。
|
||||
- [ ] `npm run format:check`、`npm run test`、`npm run test:client`、`npm run build` 全绿。
|
||||
26
.trellis/tasks/08-26-direct-edit-solver-api/task.json
Normal file
26
.trellis/tasks/08-26-direct-edit-solver-api/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "direct-edit-solver-api",
|
||||
"name": "direct-edit-solver-api",
|
||||
"title": "约束求解器与编辑 API",
|
||||
"description": "resolveDirectEditConstraints 纯函数边界,以及预览/保存/revision/rebase API",
|
||||
"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