User pointed at the palette's PNG icons in the bottom toolbar — they
expected the inspector title to use the same artwork, not the iconify
lucide glyphs. Different visual style.
- IconRef gains `{ kind: 'url'; src: string }`. Plain `<img>` render
in ParametricInspector (no next/image — the inspector is
`'use client'`).
- All currently-registered kinds switched to URL refs matching their
palette `iconSrc`:
fence → /icons/fence.png
slab → /icons/floor.png
ceiling → /icons/ceiling.png
wall → /icons/wall.png
spawn → /icons/site.png
shelf → /icons/column.png (placeholder, same as palette)
- Kind-owned panels (slab/ceiling) already pass URL strings to their
own PanelWrapper; unchanged.
Door/window/item will get URL refs when they register at Stage A.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each tool is now a direct copy of the legacy implementation, relocated
under @pascal-app/nodes/<kind>/ and dispatched via the registry's
def.affordanceTools. No DragAction abstraction, no clever live-drag
exception, no novel snap pipeline — same code, same UX, same
performance, same history dance.
Ports:
- fence/curve-tool.tsx (legacy CurveFenceTool, 1:1)
- fence/move-tool.tsx (legacy MoveFenceTool, 1:1 — including
the mesh.position + useLiveTransforms
exception that the legacy uses for fence
specifically)
- wall/curve-tool.tsx (legacy CurveWallTool, 1:1)
- slab/move-tool.tsx (legacy MoveSlabTool, 1:1)
- ceiling/move-tool.tsx (legacy MoveCeilingTool, 1:1 — preview
fill + outline overlay preserved)
Drops the obsolete DragAction-based action files
(packages/nodes/src/{fence,wall,slab,ceiling}/actions/{curve,move}.ts)
and their now-empty actions/ directories where applicable. Fence
keeps actions/move-endpoint.ts since that port works.
Editor public surface gains `getWallGridStep` + `snapScalarToGrid`
(transitional exports — Stage F moves them into @pascal-app/nodes).
ToolManager + MoveTool dispatch unchanged: the same legacy-fallback
branches now mount the registry component because the affordances are
declared, but the rendered behavior matches the legacy because the
implementations are copies.
Per-kind progress: fence D ✅ (curve / move-endpoint / move / placement
all kind-owned), slab D ✅, ceiling D ✅, wall D 🟡 (curve only,
endpoint/move/placement still legacy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-reported regressions made it clear that my D-ports of the curve
and whole-item move tools introduced more friction than they removed.
The legacy CurveFenceTool / CurveWallTool / MoveFenceTool /
MoveSlabTool / MoveCeilingTool ship more polish than the new ports do
right now:
- Legacy curve tools pre-snap the pointer position to the 0.5m grid
BEFORE projecting onto the chord normal, and they Shift-toggle to
a free-place mode. The ports skipped both — finer math, but the
user-visible UX regressed (laggy because the cascade resolver fires
per move, history feels broken near the no-op threshold).
- Legacy whole-item moves use scene.update per tick which keeps
hosted children visually aligned. The live-drag mesh.position port
cleared the offset before the GeometrySystem could rebuild,
producing a one-frame teleport on commit.
Drop the affordance registrations for those tools — the ToolManager /
MoveTool dispatch falls back to the legacy per-kind tools when the
registry doesn't declare the affordance. Stage D progress preserved
for: fence move-endpoint (linked cascade + alt-detach), slab/ceiling
boundary + hole editors, fence placement, slab placement, ceiling
placement.
The kind-owned files (curve-tool.tsx, move-tool.tsx, actions/curve.ts,
actions/move.ts) stay on disk for the next iteration — when they
reach parity with legacy UX, re-add the affordance entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replicates the slab Stage D recipe for ceiling: four affordances
routed through the registry — def.tool for the placement flow,
def.affordanceTools for boundary edit / hole edit / whole-ceiling move.
Ceiling-specific bits preserved:
- Placement tool keeps the dual-cursor + vertical TSL-gradient
connector + ground-shadow lines (1:1 with legacy).
- Move tool wrapper renders the translucent preview fill + outline
overlay so the user sees the destination before clicking.
ToolManager mount sites for CeilingBoundaryEditor / CeilingHoleEditor
now route through `getRegistryAffordanceTool` with legacy fallback.
Per-kind progress: ceiling A ✅ B (n/a, def.renderer escape hatch
preserved) C ✅ D ✅; E + F pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Depth-first session: drive registered kinds through Stage B (pure
def.geometry, drop system re-export) and Stage C (def.floorplan,
short-circuit legacy inline rendering in floorplan-panel.tsx).
spawn → C
- buildSpawnFloorplan wired on definition (was written but deferred
to avoid double-render).
- floorplan-panel.tsx's floorplanSpawnEntries useMemo short-circuits
to [] when nodeRegistry.has('spawn').
fence → B
- generateFenceGeometry exported from viewer; buildFenceGeometry
wraps it in a Group+Mesh with DEFAULT_STAIR_MATERIAL.
- def.geometry set; renderer + system fields dropped.
- Deleted nodes/src/fence/{renderer.tsx,system.tsx}.
fence → C
- buildFenceFloorplan: polyline along centerline (sampled for curved
fences via sampleWallCenterline from core). Stroke width = node.thickness.
- floorplan-panel.tsx's floorplanFenceEntries short-circuits.
slab → B
- generateSlabGeometry exported from viewer; buildSlabGeometry wraps
it in a Group+Mesh + cached material (preset / custom / default
pattern preserved from legacy renderer).
- def.geometry set; renderer + system fields dropped.
- Deleted nodes/src/slab/{renderer.tsx,system.tsx}.
slab → C
- buildSlabFloorplan: SVG path with outer polygon + hole subpaths
(uses getRenderableSlabPolygon from core for wall-clipping parity).
- floorplan-panel.tsx's slabPolygons short-circuits.
ceiling → B INTENTIONALLY SKIPPED
- Ceiling renderer renders React children (hosted items) + uses TSL
shader materials + named meshes that other systems poke
(getObjectByName('ceiling-grid')). Pure def.geometry can't preserve
that. Ceiling keeps def.renderer (the custom escape hatch) — same
pattern item uses. Documented in ceiling/definition.ts.
ceiling → C
- buildCeilingFloorplan: dashed-outline path with hole subpaths
(visually distinct from slab since ceilings are above).
- floorplan-panel.tsx's ceilingPolygons short-circuits.
Per-kind progress after this session:
- shelf: B ✅ C ✅ (Stage E since brand-new)
- spawn: A ✅ C ✅
- wall: A ✅ (B blocked on ctx.levelData design)
- fence: A ✅ B ✅ C ✅
- slab: A ✅ B ✅ C ✅
- ceiling: A ✅ C ✅ (B intentionally not applicable)
- door / window / item: A ✅ (B+C pending in future sessions)
Known test issue: `bun test` in packages/nodes fails to load
`three-bvh-csg` through the viewer's transitive imports (UMD/ESM
mismatch in Bun's test runner). The Next.js editor build works fine
because it bundles differently. Fix requires either dynamic imports
(breaks sync def.geometry contract) or test env config — deferred.
Other tests (schema, geometry, parity) pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>