Commit Graph

28 Commits

Author SHA1 Message Date
4ef7b478b2 chore(task): archive 08-26-direct-edit-map-editor 2026-08-28 10:25:42 +08:00
ef55556f19 chore(task): archive 08-27-junction-dominated-roads 2026-08-28 10:19:25 +08:00
92297270f1 feat: drag road handles with ghost and server preview
Three things, kept in one commit because they touch overlapping hunks of the
same two files and this environment has no interactive hunk staging. Splitting
them by file would have drawn boundaries that misrepresent what changed.

1. Step 4 of the map editor. A native OpenLayers PointerInteraction turns a
   drag into a clamped constraint value, the ghost source shows it immediately,
   and the solver's answer replaces a parallel set of preview layers while the
   baseline layers are hidden rather than overwritten. Preview requests debounce
   at 80 ms, pointerup flushes without waiting, and a newer request aborts the
   one in flight; EditSession decides which answers count. Handle positions come
   from the clamped value, so a handle stops at its limit instead of following
   the cursor. Three of the four drag capabilities are live: edge offset,
   sidewalk width, lane divider.

2. Road edge handles were drawn on the wrong side. offsetLine() offsets
   counter-clockwise from the direction of travel and sidewalks use
   `heading + (side === 'left' ? -90 : 90)`, so left is `tangent - 90`;
   makeRoadHandles() placed the left handle at `tangent + 90`, over the right
   kerb. Dragging the visually-left handle moved the right edge. Fixed on both
   sides of the wire, with regression tests that name the sides geographically
   rather than by axis sign.

3. Roads the junctions geometrically fill are now read-only. The 0.45 cap per
   reserve made the existing `unavailable` branch unreachable, so a 14.5 m stub
   between two junctions was offered a 1.5 m editable band with no room for the
   transitions a road-interval constraint needs. Greying only affects the
   manifest: constraints already saved against such a road keep being solved, so
   the geometry output is unchanged and the fixture baselines do not move.

Range handles are built and unit-tested but hidden behind
`intervalEditingSupported`: compileGeometry() reads neither profile.interval nor
profile.transitions, so every edit applies to the whole road and the control
would have had no effect. Recorded in research/interval-not-applied.md, which
also blocks one PRD acceptance criterion.

The ol-ext probe stays in the tree as a manual harness; ol-ext is still not a
dependency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:38:34 +08:00
bc4b9a9717 feat: add direct edit handles behind directEdit flag
Steps 1-3 of the main map road interval editor.

EditSession keeps the command stack, undo/redo and previewSeq arbitration as
pure logic with no OpenLayers reference, so all of it is unit-tested in node.
Pointer displacement converts to meters through EPSG:4326 and spherical
distance: treating a 3857 delta as meters desyncs the geometry from the cursor
by 1/cos(latitude). Handle drags project onto the axis the manifest declares
and clamp to its range, so the client never writes a coordinate into a road
polygon.

All of it sits behind a directEdit flag that defaults to off. With the flag off
the workbench requests no manifest, creates no extra source and registers no
interaction, so behaviour matches main.

The ol-ext probe passed its three gates but is not adopted for road handles.
Transform translates by the raw pointer delta, so a handle detaches from its
clamped constraint value: a drag reading -24.1 m produced a draft of -5.4 m.
Production needs the handle position derived from the constraint instead, which
means owning the position update, so native OL PointerInteraction will carry
the drag. ol-ext stays out of package.json; the probe is kept as a manual
harness. Reserve handles are unreachable with the current solver, recorded in
research/ rather than worked around.

Also names the dead backend when an API response is empty, instead of
surfacing "Unexpected end of JSON input" from response.json().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 14:59:11 +08:00
e25c564bb9 chore: record journal 2026-08-27 13:30:19 +08:00
2d85211558 chore(task): archive 08-26-direct-edit-solver-api 2026-08-27 13:29:18 +08:00
a26d4be666 docs: document direct edit solver and API contracts 2026-08-27 13:25:50 +08:00
94e319c717 chore: record direct edit solving progress 2026-08-27 12:37:32 +08:00
67e67e66bf chore: record direct edit manifest progress 2026-08-27 12:19:54 +08:00
3297d0c97a feat: insert direct edit constraint solver stage
Adds resolveDirectEditConstraints between compileRoadModel and
compileGeometry, per the parent design's data flow. The stage is an
identity transform until the individual constraint kinds land: it
returns empty road profiles, junction plans and handle manifest, so
compileGeometry's geometry code is untouched and output stays
byte-identical.

Constraint planning is separated from solving up front. Only `exact`
and `recheck` constraints may reach the solver; `disabled`,
`pending`, `conflicted` and `stale` are reported as unapplied with a
reason. SOLVED_KINDS starts empty and grows one entry per kind, so a
partially delivered solver reports what it skipped instead of
publishing half-solved geometry.

diagnostic() moves to src/compile/diagnostics.js so the solver can
share the compiler's diagnostic shape without depending on fs, which
would break the pure-function boundary that lets preview, the full
compile and the CLI export share one implementation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 10:29:45 +08:00
c5e762e1a9 chore: record journal 2026-08-27 09:06:40 +08:00
98e8a770a3 chore(task): archive 08-26-direct-edit-documents 2026-08-27 09:01:21 +08:00
ba8eaba9d9 feat: compile revisions from area config snapshots 2026-08-26 18:08:55 +08:00
c0c8a16dd4 feat: add immutable road revision storage 2026-08-26 17:54:38 +08:00
35002371ae feat: add native road edits document validation 2026-08-26 17:46:02 +08:00
93f09e399e 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>
2026-08-26 17:33:42 +08:00
7dc7ede2b8 chore(task): archive 08-26-workbench-selection-save-fix 2026-08-26 15:39:18 +08:00
02c3a4d3e4 fix: avoid map refresh on road selection 2026-08-26 15:39:17 +08:00
89e0a9ce37 chore(task): archive 00-bootstrap-guidelines 2026-08-26 15:29:12 +08:00
faaa33717f docs: add frontend development guidelines 2026-08-26 15:29:11 +08:00
f7601d554e chore(task): archive 08-26-workbench-layer-scene-polish 2026-08-26 15:26:42 +08:00
3c5f39ed73 fix: refine workbench map layers 2026-08-26 15:26:41 +08:00
2f2bd6cbf0 chore(task): archive 08-26-scene-preview-fix 2026-08-26 15:09:27 +08:00
4133a5244a fix: restore scene preview styles 2026-08-26 15:09:27 +08:00
89b402de7a chore(task): archive 08-26-react-workbench-migration 2026-08-26 15:02:14 +08:00
3ddb33e321 feat: migrate workbench to React 2026-08-26 15:01:07 +08:00
f15e69c868 chore: archive web OSM import task 2026-08-26 14:06:27 +08:00
181e575e1e feat: add web OSM import workflow 2026-08-26 14:05:26 +08:00