@vgai/engine 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,191 +0,0 @@
1
- /**
2
- * Project tools — W4/W3 of `docs/DATA-TOOLS-DESIGN.md` (§3 contract).
3
- *
4
- * A project tool is ONE file, `src/tools/<name>.tool.tsx`, discovered by
5
- * folder scan (same physics as data assets, §3.1: zero registration, delete
6
- * the file → gone). The file exports exactly two things:
7
- *
8
- * ```tsx
9
- * export const tool = defineTool({
10
- * id: 'tuning', // stable slug — becomes the tab's test id
11
- * title: 'Tuning', // the tab label (rendered after a ⚙ marker)
12
- * placement: 'document', // project-global dashboard (§3.2; 'dock' = deprecated alias)
13
- * });
14
- * export default function Tuning() { … } // plain React
15
- * ```
16
- *
17
- * or, selection-scoped (W3 — §3.2's second placement):
18
- *
19
- * ```tsx
20
- * export const tool = defineTool({
21
- * id: 'spin',
22
- * title: 'Spin',
23
- * placement: 'inspector', // selection-scoped section
24
- * match: (node) => node?.kind === 'mesh', // same contract as the
25
- * }); // inspector-section registry
26
- * export default function Spin({ node }: InspectorToolProps) { … }
27
- * ```
28
- *
29
- * The editor renders dock tools as visually distinct tabs after a separator
30
- * at the end of the bottom dock's tab row (§3.5); inspector tools render as
31
- * a ⚙-marked section in the Inspector rail whenever `match` accepts the
32
- * current selection. Both render inside an error boundary — a crashing tool
33
- * never takes the editor down. Tools are EDITOR-ONLY: no game build bundles
34
- * `src/tools/` (§4 — nothing in game code imports it), so dev/cheat surfaces
35
- * can never leak into a tester serve.
36
- *
37
- * This module is deliberately react-free metadata (the engine core outside
38
- * `src/react/` must not import react — `test/react-core-import-ban.test.ts`);
39
- * the react-facing tool hooks live in `@engine/react/use-data`, and the
40
- * editor-styled widget kit tools build panels from is `@editor/widgets`.
41
- */
42
-
43
- import type { AuthoringAdapter, EditorNode } from '../adapter';
44
-
45
- /**
46
- * Tool placements. Since W0 of the editor workspace-shell program
47
- * (docs/EDITOR-WORKSPACE-SHELL-TODO.md §7.2), placement describes MEANING,
48
- * not a physical container:
49
- *
50
- * - `'document'` — a substantial project-global surface (tuning dashboard,
51
- * table, graph) that belongs in the center workspace;
52
- * - `'inspector'` — a selection-scoped section in the right Inspector rail;
53
- * - `'utility'` — a transient output/debug surface for the bottom drawer;
54
- * - `'dock'` — DEPRECATED physical compatibility alias. Existing dock tools
55
- * remain in the bottom panel so projects do not lose the ability to tune
56
- * them while watching the Game/Scene.
57
- *
58
- * Only an explicit `document` declaration replaces the center subject.
59
- * `utility` and legacy `dock` remain in the bottom drawer. Deliberately
60
- * still nothing else — no floating windows, no in-game overlay
61
- * host (docs/DATA-TOOLS-DESIGN.md §3.2 "deliberately NOT built").
62
- */
63
- export type ToolPlacement = 'document' | 'inspector' | 'utility' | 'dock';
64
-
65
- /** The project-global (non-selection-scoped) placements. */
66
- export type GlobalToolPlacement = 'document' | 'utility' | 'dock';
67
-
68
- /**
69
- * Compatibility means preserving behavior: legacy `'dock'` tools remain in
70
- * the bottom panel. New tools choose `'document'`, `'utility'`, or
71
- * `'inspector'` deliberately.
72
- */
73
- export function normalizeToolPlacement(
74
- placement: ToolPlacement,
75
- ): 'document' | 'inspector' | 'utility' {
76
- return placement === 'dock' ? 'utility' : placement;
77
- }
78
-
79
- /** Fields every tool declares — see {@link defineTool} for the contract. */
80
- interface ToolConfigBase {
81
- /**
82
- * Stable slug for this tool: letters/digits/`-`/`_` only. It becomes the
83
- * mount's `data-testid` (`bottom-tab-tool-<id>` for dock tools,
84
- * `inspector-tool-<id>` for inspector tools), so keep it short and never
85
- * rename it casually — automation and saved layouts key off it.
86
- */
87
- id: string;
88
- /** Human label: the dock tab text, or the inspector section's header,
89
- * rendered after the ⚙ project-tool marker (§3.5). */
90
- title: string;
91
- }
92
-
93
- /** A project-global tool (§3.2 W4; `'dock'` preserves its historical bottom
94
- * placement through the `'utility'` compatibility normalization). */
95
- export interface GlobalToolConfig extends ToolConfigBase {
96
- placement: GlobalToolPlacement;
97
- }
98
-
99
- /** A selection-scoped Inspector section (§3.2, W3). */
100
- export interface InspectorToolConfig extends ToolConfigBase {
101
- placement: 'inspector';
102
- /**
103
- * When this section appears — the SAME contract as the editor's
104
- * `inspector-section-registry`: called with the currently selected node
105
- * (`null` in the no-selection/environment state) and the active authoring
106
- * adapter; return `true` to render. Keep it cheap and pure — it runs on
107
- * every inspector render. A `match` that throws is treated as `false`
108
- * (with one console teaching error), never crashing the inspector.
109
- */
110
- match: (node: EditorNode | null, adapter: AuthoringAdapter) => boolean;
111
- }
112
-
113
- /** Metadata for a project tool — a discriminated union on `placement`. */
114
- export type ToolConfig = GlobalToolConfig | InspectorToolConfig;
115
-
116
- /**
117
- * Props the editor passes to an INSPECTOR tool's default-exported component
118
- * (dock tools receive no props). Everything richer (live game store, field
119
- * addresses) arrives through the §3.3 hooks, not through more props.
120
- */
121
- export interface InspectorToolProps {
122
- /** The selected node your `match` accepted (`null` in the no-selection
123
- * state, if your `match` chose to accept it). */
124
- node: EditorNode | null;
125
- /** The selected node's stable id (`null` in the no-selection state). */
126
- nodeId: string | null;
127
- }
128
-
129
- const TEMPLATE_EXAMPLE = 'the template worked example: src/tools/example.tool.tsx';
130
-
131
- /** Teaching-error helper (§6.5): every rejection names the fix and cites the
132
- * template example by path. */
133
- function toolError(problem: string, fix: string): Error {
134
- return new Error(`defineTool: ${problem} — ${fix} (see ${TEMPLATE_EXAMPLE}).`);
135
- }
136
-
137
- /**
138
- * Validate and freeze a project tool's metadata (docs/DATA-TOOLS-DESIGN.md
139
- * §3.1). Call it once at module scope of a `src/tools/<name>.tool.tsx` file
140
- * and export the result as `tool`, next to a default-exported React
141
- * component — the editor's folder scan picks the file up automatically.
142
- *
143
- * Fails loud (§6.5 "errors teach") on anything malformed rather than letting
144
- * a half-shaped tool render confusingly: bad `id`/`title`, an unknown
145
- * placement, an inspector tool without a callable `match`.
146
- *
147
- * The returned config is frozen: tool metadata is a definition, not state
148
- * (the same immutability rule data assets follow, §2.1).
149
- */
150
- export function defineTool(config: ToolConfig): Readonly<ToolConfig> {
151
- if (config === null || typeof config !== 'object') {
152
- throw toolError(
153
- 'expected a config object',
154
- "call it as defineTool({ id, title, placement: 'document' })",
155
- );
156
- }
157
- const { id, title, placement } = config;
158
- if (
159
- placement !== 'document' &&
160
- placement !== 'utility' &&
161
- placement !== 'dock' &&
162
- placement !== 'inspector'
163
- ) {
164
- throw toolError(
165
- `unknown placement ${JSON.stringify(placement)}`,
166
- "the placements are 'document' (project-global center document), 'inspector' " +
167
- "(selection-scoped section) and 'utility' (transient bottom-drawer output); " +
168
- "'dock' remains accepted as a deprecated bottom-panel alias " +
169
- '(docs/EDITOR-WORKSPACE-SHELL-TODO.md §7.2)',
170
- );
171
- }
172
- if (typeof id !== 'string' || !/^[a-zA-Z0-9_-]+$/.test(id)) {
173
- throw toolError(
174
- `invalid id ${JSON.stringify(id)}`,
175
- 'pass a non-empty slug of letters, digits, "-" or "_" — it becomes the tool\'s data-testid (bottom-tab-tool-<id> / inspector-tool-<id>)',
176
- );
177
- }
178
- if (typeof title !== 'string' || title.trim() === '') {
179
- throw toolError(`invalid title ${JSON.stringify(title)}`, 'pass a short human-readable label');
180
- }
181
- if (placement === 'inspector') {
182
- if (typeof config.match !== 'function') {
183
- throw toolError(
184
- `placement 'inspector' requires a match function (got ${JSON.stringify(config.match)})`,
185
- "pass match: (node) => boolean — e.g. match: (node) => node?.kind === 'mesh' — deciding which selections show this section",
186
- );
187
- }
188
- return Object.freeze({ id, title, placement, match: config.match });
189
- }
190
- return Object.freeze({ id, title, placement });
191
- }