IconRef: add url kind, point registered kinds at palette assets

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>
This commit is contained in:
Wassim SAMAD
2026-05-18 10:41:45 -04:00
co-authored by Claude Opus 4.7
parent 8c2b03f99b
commit 6a1d853dd8
8 changed files with 16 additions and 6 deletions
+4
View File
@@ -230,6 +230,10 @@ export type IconRef =
/** Iconify identifier, e.g. `lucide:square`. Matches the @iconify-react /** Iconify identifier, e.g. `lucide:square`. Matches the @iconify-react
* setup the editor app already uses for tool icons. */ * setup the editor app already uses for tool icons. */
| { kind: 'iconify'; name: string } | { kind: 'iconify'; name: string }
/** URL path to a raster or vector asset (PNG/SVG/...). Matches the
* palette's PNG/SVG assets — use this to share the same artwork
* between the bottom toolbar and the inspector title. */
| { kind: 'url'; src: string }
/** Inline SVG path data. Use for asset packs or plugins that want a custom /** Inline SVG path data. Use for asset packs or plugins that want a custom
* mark without contributing a React component. */ * mark without contributing a React component. */
| { kind: 'svg'; viewBox: string; path: string } | { kind: 'svg'; viewBox: string; path: string }
@@ -137,6 +137,12 @@ export function ParametricInspector() {
function renderIcon(ref: IconRef | undefined): React.ReactNode | undefined { function renderIcon(ref: IconRef | undefined): React.ReactNode | undefined {
if (!ref) return undefined if (!ref) return undefined
if (ref.kind === 'url') {
// Plain <img> here so the inspector doesn't pull in next/image's
// server-only requirements (the file is `'use client'`). Same
// 16x16 box the legacy panels use.
return <img alt="" className="h-4 w-4 shrink-0 object-contain" src={ref.src} />
}
if (ref.kind === 'iconify') { if (ref.kind === 'iconify') {
return <Icon height={16} icon={ref.name} width={16} /> return <Icon height={16} icon={ref.name} width={16} />
} }
+1 -1
View File
@@ -86,7 +86,7 @@ export const ceilingDefinition: NodeDefinition<typeof CeilingNode> = {
presentation: { presentation: {
label: 'Ceiling', label: 'Ceiling',
description: 'A polygon-bounded ceiling surface that hosts ceiling-mounted items.', description: 'A polygon-bounded ceiling surface that hosts ceiling-mounted items.',
icon: { kind: 'iconify', name: 'lucide:square-dashed' }, icon: { kind: 'url', src: '/icons/ceiling.png' },
paletteSection: 'structure', paletteSection: 'structure',
paletteOrder: 40, paletteOrder: 40,
}, },
+1 -1
View File
@@ -94,7 +94,7 @@ export const fenceDefinition: NodeDefinition<typeof FenceNode> = {
presentation: { presentation: {
label: 'Fence', label: 'Fence',
description: 'A straight or curved fence segment with configurable posts and infill.', description: 'A straight or curved fence segment with configurable posts and infill.',
icon: { kind: 'iconify', name: 'lucide:fence' }, icon: { kind: 'url', src: '/icons/fence.png' },
paletteSection: 'structure', paletteSection: 'structure',
paletteOrder: 20, paletteOrder: 20,
}, },
+1 -1
View File
@@ -64,7 +64,7 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
presentation: { presentation: {
label: 'Shelf', label: 'Shelf',
description: 'A horizontal surface for stacking other items.', description: 'A horizontal surface for stacking other items.',
icon: { kind: 'iconify', name: 'lucide:layers' }, icon: { kind: 'url', src: '/icons/column.png' },
paletteSection: 'structure', paletteSection: 'structure',
paletteOrder: 50, paletteOrder: 50,
}, },
+1 -1
View File
@@ -83,7 +83,7 @@ export const slabDefinition: NodeDefinition<typeof SlabNode> = {
presentation: { presentation: {
label: 'Slab', label: 'Slab',
description: 'A polygon-bounded floor surface that hosts items on top.', description: 'A polygon-bounded floor surface that hosts items on top.',
icon: { kind: 'iconify', name: 'lucide:square' }, icon: { kind: 'url', src: '/icons/floor.png' },
paletteSection: 'structure', paletteSection: 'structure',
paletteOrder: 30, paletteOrder: 30,
}, },
+1 -1
View File
@@ -52,7 +52,7 @@ export const spawnDefinition: NodeDefinition<typeof SpawnNode> = {
presentation: { presentation: {
label: 'Spawn Point', label: 'Spawn Point',
description: 'Player or camera origin within a level. One per level.', description: 'Player or camera origin within a level. One per level.',
icon: { kind: 'iconify', name: 'lucide:flag' }, icon: { kind: 'url', src: '/icons/site.png' },
paletteSection: 'structure', paletteSection: 'structure',
paletteOrder: 90, // bottom of structure list — matches legacy palette order paletteOrder: 90, // bottom of structure list — matches legacy palette order
}, },
+1 -1
View File
@@ -82,7 +82,7 @@ export const wallDefinition: NodeDefinition<typeof WallNode> = {
presentation: { presentation: {
label: 'Wall', label: 'Wall',
description: 'A straight or curved wall segment. Hosts doors, windows, and wall-mounted items.', description: 'A straight or curved wall segment. Hosts doors, windows, and wall-mounted items.',
icon: { kind: 'iconify', name: 'lucide:wall' }, icon: { kind: 'url', src: '/icons/wall.png' },
paletteSection: 'structure', paletteSection: 'structure',
paletteOrder: 10, paletteOrder: 10,
}, },