feat: scope the handle manifest and re-aim the ghost
Two consequences of the preview dropping from ~1535 ms to ~142 ms. The manifest is now scoped to the object being edited. It was 844 KB on a 41-road workspace — 239 handles, 89% of the bytes being `affects` id lists at 46 ids per junction handle — shipped on every preview while the map rendered six. A segment selection returns 12.2 KB, a 69x reduction, and the cost no longer multiplies with each handle kind we are about to add. Omitting the selection keeps the full manifest for the compiler and existing callers, and the tests assert that scoping is a filter of the full manifest rather than a second derivation. The ghost is re-aimed from "estimated geometry" to "what you asked for". Its guide line existed to mark the origin through a long wait that no longer happens, so it is gone; what remains is what the preview cannot say — the numeric delta and whether the drag has hit its clamp. The translucent outline research/joint-solver.md asked for is deliberately not built: drawing it accurately means recomputing the road surface in the browser, which the design forbids, and drawing it crudely would be wrong exactly at transitions, junction boundaries and clamps. A preview that lies is worse than none. `degraded` finally has a consumer. EditSession has tracked it since the session work but nothing read it; a slow solve now dims the ghost in place with a pending label instead of clearing it and letting the geometry flicker, as design.md requires. Selection also fixed a latent hazard: the manifest effect reloads on every selection change, and it used to call session.load() each time, which would have discarded unsaved edits the moment the user clicked another road. It now adopts a document only when the compiled document actually changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -684,6 +684,38 @@ function makeJunctionHandles(model, junctionData, constraints) {
|
||||
return handles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which handles a selection wants.
|
||||
*
|
||||
* Whole-manifest responses are expensive: on a 41-road area the handles are
|
||||
* 844 KB, 89% of it the `affects` id lists (46 ids per junction handle), and the
|
||||
* map renders about six of them. `selection` therefore scopes the manifest to the
|
||||
* object being edited. Omitting it keeps the full manifest, which the compiler
|
||||
* and the existing callers rely on.
|
||||
*
|
||||
* The two selection types match the two editors: the main map owns a road
|
||||
* segment's cross-section, JunctionTools owns a node.
|
||||
*/
|
||||
function selectHandles(handles, model, selection) {
|
||||
if (!selection || !selection.type) return handles;
|
||||
if (selection.type === 'segment') {
|
||||
const segmentOf = new Map((model.roads || []).map((road) => [road.id, road.segmentId]));
|
||||
// Road kinds only: reserve interiors belong to JunctionTools, and the main map
|
||||
// must not offer a second way to edit them.
|
||||
return handles.filter(
|
||||
(handle) =>
|
||||
handle.kind.startsWith('road-') &&
|
||||
handle.anchor &&
|
||||
segmentOf.get(handle.anchor.roadId) === String(selection.id),
|
||||
);
|
||||
}
|
||||
if (selection.type === 'junction')
|
||||
return handles.filter(
|
||||
(handle) => handle.kind.startsWith('junction-') && handle.anchor?.nodeId === String(selection.id),
|
||||
);
|
||||
return handles;
|
||||
}
|
||||
|
||||
function resolveDirectEditConstraints(model, editDocument = null, context = {}) {
|
||||
const diagnostics = [];
|
||||
const constraints = Array.isArray(editDocument && editDocument.constraints) ? editDocument.constraints : [];
|
||||
@@ -693,10 +725,14 @@ function resolveDirectEditConstraints(model, editDocument = null, context = {})
|
||||
const reconciled = reconcileConstraints(model || {}, constraints, editDocument?.base, junctionData, context);
|
||||
const solved = solveConstraints(model || {}, reconciled, junctionData, diagnostics);
|
||||
handles.reserves = junctionData.reserves;
|
||||
handles.handles = [
|
||||
...makeRoadHandles(model || {}, groups, handles.reserves, reconciled),
|
||||
...makeJunctionHandles(model || {}, junctionData, reconciled),
|
||||
];
|
||||
handles.handles = selectHandles(
|
||||
[
|
||||
...makeRoadHandles(model || {}, groups, handles.reserves, reconciled),
|
||||
...makeJunctionHandles(model || {}, junctionData, reconciled),
|
||||
],
|
||||
model || {},
|
||||
context.selection,
|
||||
);
|
||||
return {
|
||||
schema: RESOLUTION_SCHEMA,
|
||||
roadProfiles: solved.roadProfiles,
|
||||
|
||||
Reference in New Issue
Block a user