Add preview slot to NodeDefinition; show translucent shape during move

User feedback: the move tool's CursorSphere is just a vertical line,
hard to tell what you're moving. Placement gets a translucent shape
that follows the cursor; move should too.

NodeDefinition.preview?: () => Promise<{ default: ComponentType<{ node }> }>
opt-in lazy component that renders a translucent ghost of the node.
Used by:
- The placement tool (ShelfTool) — renders the preview at the cursor
  position so the user sees the shape they're placing.
- The move tool (MoveRegistryNodeTool) — renders the preview at the
  drag target alongside the CursorSphere. Plus the original node is
  also dragged via live transforms, so the user sees both: the actual
  node moving + a translucent ghost at the same spot.

Implementation:
- New nodes/shelf/preview.tsx: ShelfPreview component. Renders the
  same shape as ShelfRenderer but `transparent: true, opacity: 0.5`.
- shelfDefinition.preview = () => import('./preview').
- ShelfTool's placement preview now uses <ShelfPreview node={defaults} />
  instead of an inline copy of the box geometry.
- MoveRegistryNodeTool lazy-loads `def.preview` (cached by loader,
  Suspense-wrapped). If a kind doesn't define `preview`, only the
  CursorSphere shows — matches today's behavior.

Phase 4 may merge `preview` with `renderer` behind an `opacity` prop
so kinds don't duplicate JSX between the solid and translucent
versions; until then defining `preview` is opt-in and one extra file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-14 16:01:32 -04:00
co-authored by Claude Opus 4.7
parent a090c22f42
commit e84b1ec8bc
5 changed files with 130 additions and 52 deletions
+9
View File
@@ -32,6 +32,15 @@ export type NodeDefinition<S extends ZodObject<any>> = {
tool?: LazyComponent
affordances?: Affordance<z.infer<S>>[]
/**
* Optional translucent preview of the node — used by the move tool to
* show where the node will land, and by the placement tool's cursor.
* Receives the partially-resolved node (or a default-shaped stub during
* placement before any commit has happened). Phase 4 may merge this with
* the renderer behind an `opacity` prop.
*/
preview?: () => Promise<{ default: ComponentType<{ node: z.infer<S> }> }>
presentation?: Presentation
mcp?: McpOverrides
}