#407 ("Always-visible placement ghosts + true-nearest 2D opening snap")
restructured the door/window placement tools: it split the old
create-in-resolve into a pure resolveWallPlacement() + side-effecting
applyWallTarget(), added an off-host floating ghost (fallbackPose / showGhostAt),
unified wall hover into onWallHover, and extracted commit{Door,Window}AtWall.
Conflict resolution (door/tool.tsx, window/tool.tsx):
- Re-homed the single publishOpeningGuidesForWallEvent() call into applyWallTarget
(after the draft update + updateCursor), using that scope (wall,
getSlabElevationForWall(wall)); door includeVertical:false, window true.
- Routed clearOpeningGuides3D() through showGhostAt so every off-host fallback
path clears; kept clears in hideCursor, commit helpers, onRoofHover, teardown.
- Made the window sill snap (resolvePlacementY) event-free and call it from the
pure resolveWallPlacement, so hover + click both get sill/centre/top snapping;
Shift bypasses, the moving draft is excluded via ignoreId.
- Dropped the branch's inline onWallClick in favour of #407's onWallClick +
commitWindowAtWall (no behavior lost).
- Reconstructed both files' import blocks, which the auto-merge had truncated to
stubs (only tsc caught it).
All other conflicts auto-merged (registry types, floorplan-registry-layer,
both move-tools). Verified: typecheck 9/9, biome clean, nodes 169 + core 594
tests pass, editor `bun run build` 7/7. Merge resolution reviewed by Codex
(adversarial): no semantic regressions; all #407 behavior preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>