editor: tool-defaults seeding + drawTool capability (fence presets) (#346)

* feat(editor): tool-defaults seeding + drawTool capability; fence consumes it

Adds a generic, transient `useEditor.toolDefaults` slice keyed by tool, set
via `setToolDefaults(tool, params)`. A draw tool's create path merges its
entry when minting a node and clears it on deactivation, so a host app can
prime the next-drawn node's parameters — placing a saved preset of a drawn
kind, or a future "small / medium / large" dimension picker for
wall / slab / ceiling.

Marks the kind with `capabilities.drawTool` (helper `isDrawnViaTool`) so host
apps know to route placement through `setToolDefaults(type) + setTool(type)`
instead of cloning a finished instance.

Wires fence end-to-end: it declares `drawTool: true`, its create path merges
`toolDefaults.fence`, and the draft preview (bar geometry, cursor, HUD label
heights) reflects the seeded height/thickness so the ghost matches what will
be built. The tool clears its own defaults on unmount.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(editor): restore dropped useEditor import in fence tool

The toolDefaults-seeding commit lost the `useEditor` import (formatter
stripped it), shipping a runtime ReferenceError when FenceTool mounts.
Re-add it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-29 08:36:51 -04:00
committed by GitHub
co-authored by Claude Opus 4.7
parent a144502c04
commit b8d94a6436
9 changed files with 159 additions and 25 deletions
+2
View File
@@ -17,6 +17,8 @@ export {
discoverPlugins,
getHostRefFields,
getSelectableKinds,
isDrawnViaTool,
isDrawnViaToolKind,
isPresettable,
isPresettableKind,
isRegistryMovable,
@@ -2,6 +2,8 @@ import { beforeEach, describe, expect, test } from 'bun:test'
import { z } from 'zod'
import {
getHostRefFields,
isDrawnViaTool,
isDrawnViaToolKind,
isPresettable,
isPresettableKind,
loadPlugin,
@@ -124,6 +126,28 @@ describe('getHostRefFields', () => {
})
})
describe('isDrawnViaTool', () => {
beforeEach(() => {
nodeRegistry._reset()
})
test('true when capability set', () => {
const def = makeDefinition('fence', { capabilities: { drawTool: true } })
expect(isDrawnViaTool(def)).toBe(true)
})
test('false when unset or not exactly true', () => {
expect(isDrawnViaTool(makeDefinition('column'))).toBe(false)
expect(isDrawnViaTool(makeDefinition('off', { capabilities: { drawTool: false } }))).toBe(false)
})
test('isDrawnViaToolKind looks up the registry', () => {
registerNode(makeDefinition('fence', { capabilities: { drawTool: true } }))
expect(isDrawnViaToolKind('fence')).toBe(true)
expect(isDrawnViaToolKind('unknown')).toBe(false)
})
})
describe('loadPlugin', () => {
beforeEach(() => {
nodeRegistry._reset()
+16
View File
@@ -174,6 +174,22 @@ export function getHostRefFields(def: AnyNodeDefinition): ReadonlyArray<string>
return def.capabilities.hostRefFields ?? []
}
/**
* Whether instances of this kind are created by drawing with a build tool
* (tool id === node `type`) rather than dropping a finished instance. Read
* by host apps to route preset placement of such kinds through
* `setToolDefaults(type, params)` + `setTool(type)` — see
* `def.capabilities.drawTool` docs.
*/
export function isDrawnViaTool(def: AnyNodeDefinition): boolean {
return def.capabilities.drawTool === true
}
export function isDrawnViaToolKind(kind: string): boolean {
const def = nodeRegistry.get(kind)
return def ? isDrawnViaTool(def) : false
}
export async function loadPlugin(plugin: Plugin): Promise<void> {
if (plugin.apiVersion !== HOST_API_VERSION) {
throw new Error(
+14
View File
@@ -1008,6 +1008,20 @@ export type Capabilities = {
* are non-leaf scene containers.
*/
presettable?: boolean
/**
* Instances of this kind are created by operating a build tool and
* drawing on the grid (clicking points), rather than dropping a
* finished instance. The tool id equals the node `type`. Host apps may
* seed the tool's starting parameters via
* `useEditor.setToolDefaults(type, params)` before activating it — the
* tool's create path merges those defaults when minting the node and
* clears its own entry on deactivation. Used so placing a saved preset
* of a drawn kind contributes its build parameters (a fence's
* height / style / post spacing) while the user draws the fresh span,
* and so a future "small / medium / large" picker can prime the same
* tool. Read via the `isDrawnViaTool(def)` helper. Default `false`.
*/
drawTool?: boolean
}
/**