@volter/editor-react 0.5.67 → 0.5.69

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.
package/NOTICE CHANGED
@@ -1,4 +1,4 @@
1
1
  Copyright 2026 Volter AI, Inc.
2
2
 
3
- The files in this package were imported from volter-ai/vgai-engine at commit
3
+ The files in this package were imported from volter-ai/volter-engine at commit
4
4
  09c2749ce. LICENSE contains the AGPL-3.0-only license they retain.
@@ -2,7 +2,7 @@
2
2
  * The component-contract analyzer, registered for the browser (a `workspace.service`
3
3
  * contribution): what the kit's Content index reads when it indexes a project from storage,
4
4
  * with no session server to ask (`@volter/editor-sdk/source-analysis`). The session's server
5
- * registers the same analyzer through this package's `vgai.serving` module.
5
+ * registers the same analyzer through this package's `volter.serving` module.
6
6
  */
7
7
 
8
8
  import { registerComponentContractAnalyzer } from '@volter/editor-sdk/source-analysis';
@@ -1114,6 +1114,16 @@ ${child}${parentIndent}${code.slice(closingTagStart)}`, changed: true };
1114
1114
  function insertSiblingElement(code, elementStart, snippet) {
1115
1115
  const trimmed = snippet.trim();
1116
1116
  if (!trimmed.startsWith("<")) return { code, changed: false };
1117
+ let before = elementStart - 1;
1118
+ while (before >= 0 && /\s/.test(code[before])) before--;
1119
+ const arrowBody = code[before] === ">" && code[before - 1] === "=";
1120
+ if (before < 0 || arrowBody || code[before] !== ">" && code[before] !== "}") {
1121
+ return {
1122
+ code,
1123
+ changed: false,
1124
+ error: "this element is not a JSX child (a lone returned element), so it can have no sibling."
1125
+ };
1126
+ }
1117
1127
  const r = lineBlockRange(code, elementStart);
1118
1128
  if (!r) return { code, changed: false };
1119
1129
  const indent = code.slice(r.lineStart, elementStart).match(/^(\s*)/)?.[1] ?? "";
@@ -2708,31 +2718,31 @@ function transformSource(code, file, store, opts) {
2708
2718
  const componentInstanceExpressions = (parameters) => {
2709
2719
  const first = parameters[0];
2710
2720
  if (!first) {
2711
- sourceEdits.push({ pos: parameters.pos, text: "{ __vgaiOid, __vgaiLabel } = {}" });
2712
- return { instanceExpression: "__vgaiOid", labelExpression: "__vgaiLabel" };
2721
+ sourceEdits.push({ pos: parameters.pos, text: "{ __volterOid, __volterLabel } = {}" });
2722
+ return { instanceExpression: "__volterOid", labelExpression: "__volterLabel" };
2713
2723
  }
2714
2724
  if (ts8.isObjectBindingPattern(first.name)) {
2715
2725
  sourceEdits.push({
2716
2726
  pos: first.name.getStart(sf) + 1,
2717
- text: "__vgaiOid, __vgaiLabel, "
2727
+ text: "__volterOid, __volterLabel, "
2718
2728
  });
2719
2729
  if (!first.initializer) {
2720
2730
  sourceEdits.push({ pos: first.getEnd(), text: " = {}" });
2721
2731
  }
2722
- return { instanceExpression: "__vgaiOid", labelExpression: "__vgaiLabel" };
2732
+ return { instanceExpression: "__volterOid", labelExpression: "__volterLabel" };
2723
2733
  }
2724
2734
  if (ts8.isIdentifier(first.name) && !first.dotDotDotToken) {
2725
2735
  sourceEdits.push({
2726
2736
  pos: first.name.getStart(sf),
2727
2737
  end: first.questionToken?.end ?? first.name.getEnd(),
2728
- text: `{ __vgaiOid, __vgaiLabel, ...${first.name.text} }`
2738
+ text: `{ __volterOid, __volterLabel, ...${first.name.text} }`
2729
2739
  });
2730
2740
  if (!first.initializer) {
2731
2741
  sourceEdits.push({ pos: first.getEnd(), text: " = {}" });
2732
2742
  }
2733
- return { instanceExpression: "__vgaiOid", labelExpression: "__vgaiLabel" };
2743
+ return { instanceExpression: "__volterOid", labelExpression: "__volterLabel" };
2734
2744
  }
2735
- return { instanceExpression: "__vgaiOid", labelExpression: "__vgaiLabel" };
2745
+ return { instanceExpression: "__volterOid", labelExpression: "__volterLabel" };
2736
2746
  };
2737
2747
  const pushComponent = (name, parameters, body) => {
2738
2748
  if (!name || !/^[A-Z]/.test(name) || !parameters) return false;
@@ -2811,10 +2821,10 @@ function transformSource(code, file, store, opts) {
2811
2821
  (property2) => ts8.isJsxAttribute(property2) && property2.name.getText(sf) === "name"
2812
2822
  );
2813
2823
  const label = authoredName?.initializer && ts8.isStringLiteral(authoredName.initializer) ? authoredName.initializer.text : tag;
2814
- const labelAttribute = authoredName?.initializer && ts8.isJsxExpression(authoredName.initializer) ? `__vgaiLabel={${authoredName.initializer.expression?.getText(sf) ?? JSON.stringify(tag)}}` : `__vgaiLabel=${JSON.stringify(label)}`;
2824
+ const labelAttribute = authoredName?.initializer && ts8.isJsxExpression(authoredName.initializer) ? `__volterLabel={${authoredName.initializer.expression?.getText(sf) ?? JSON.stringify(tag)}}` : `__volterLabel=${JSON.stringify(label)}`;
2815
2825
  sourceEdits.push({
2816
2826
  pos: node.attributes.end,
2817
- text: ` __vgaiOid="${oid}" ${labelAttribute}`
2827
+ text: ` __volterOid="${oid}" ${labelAttribute}`
2818
2828
  });
2819
2829
  }
2820
2830
  } else {
@@ -3477,7 +3487,7 @@ function r3fAuthoringDiagnostics(code, file, opts) {
3477
3487
  message: `${rootComponent} animates its authored outer root through useFrame. Keep the outer root stable and animate an inner child so editor transforms do not fight runtime motion.`
3478
3488
  });
3479
3489
  }
3480
- if (/^[A-Z]/.test(tag) && contract?.root === "single" && // A package component vgai knows only through the built-in table is not
3490
+ if (/^[A-Z]/.test(tag) && contract?.root === "single" && // A package component volter knows only through the built-in table is not
3481
3491
  // a hierarchy row of its own — `<RigidBody>` never receives the editor's
3482
3492
  // stamp, so its props are attributed to the node it wraps
3483
3493
  // (`collapsedWrappersOf`). Naming it would name nothing.
@@ -3515,8 +3525,8 @@ import { existsSync as existsSync2 } from "node:fs";
3515
3525
  import { join } from "node:path";
3516
3526
 
3517
3527
  // ../editor-project/src/manifest/filename.ts
3518
- var MANIFEST_FILENAME = "vgai.project.json";
3519
- var REMOVED_MANIFEST_FILENAME = "vgai.game.json";
3528
+ var MANIFEST_FILENAME = "volter.project.json";
3529
+ var REMOVED_MANIFEST_FILENAME = "volter.game.json";
3520
3530
  function removedManifestFilenameMessage(where) {
3521
3531
  return `${where}: found \`${REMOVED_MANIFEST_FILENAME}\` and no \`${MANIFEST_FILENAME}\`. \`${REMOVED_MANIFEST_FILENAME}\` was REMOVED (the legacy-removal doctrine, docs/ARCHITECTURE-CORE.md \xA7Vocabulary) \u2014 nothing reads it any more, and it is deliberately NOT read as a fallback, because a second accepted name is the defect.
3522
3532
  Fix: rename it \u2014 \`git mv ${REMOVED_MANIFEST_FILENAME} ${MANIFEST_FILENAME}\`. The contents are unchanged; only the filename moved.`;
@@ -3534,7 +3544,7 @@ function resolveManifestPath(dir) {
3534
3544
  }
3535
3545
 
3536
3546
  // ../editor-sdk/src/session/project-serving.ts
3537
- var SOURCE_WRITE_ROUTES_PLUGIN = "vgai-ui-oid";
3547
+ var SOURCE_WRITE_ROUTES_PLUGIN = "volter-ui-oid";
3538
3548
 
3539
3549
  // ../editor-sdk/src/css-numeric-style.ts
3540
3550
  var UNITLESS_NUMBER_PROPS = /* @__PURE__ */ new Set([
@@ -3674,7 +3684,7 @@ function planDeleteStory(source, name) {
3674
3684
  if (outside.includes(`defaultStory: '${name}'`) || outside.includes(`defaultStory: "${name}"`)) {
3675
3685
  return {
3676
3686
  ok: false,
3677
- reason: `'${name}' is this file's declared defaultStory \u2014 point vgai.defaultStory at another story first`
3687
+ reason: `'${name}' is this file's declared defaultStory \u2014 point volter.defaultStory at another story first`
3678
3688
  };
3679
3689
  }
3680
3690
  return { ok: true, nextSource: outside, summary: `story '${name}' deleted` };
@@ -4236,7 +4246,7 @@ export default meta;
4236
4246
  type Story = StoryObj<typeof meta>;
4237
4247
 
4238
4248
  export const Default: Story = {
4239
- parameters: { vgai: { default: true } },
4249
+ parameters: { volter: { default: true } },
4240
4250
  };
4241
4251
  `;
4242
4252
  const importSpecifier = relativeImportSpecifier(normalizePath(request.sourceFile), componentFile);
@@ -5006,7 +5016,10 @@ function planSourceEdit(request, sources, resolveOid, utilityClasses = UTILITY_C
5006
5016
  request.ensureImport.module,
5007
5017
  request.ensureImport.kind
5008
5018
  ).code : result.code;
5009
- return planned(entry.file, src, code, { changed: result.changed });
5019
+ return planned(entry.file, src, code, {
5020
+ changed: result.changed,
5021
+ ...result.error ? { error: result.error } : {}
5022
+ });
5010
5023
  }
5011
5024
 
5012
5025
  // src/source/r3f-diagnostic-index.ts
@@ -5035,7 +5048,7 @@ function fileDiagnosticJoin(fileEntries, fileDiagnostics) {
5035
5048
 
5036
5049
  // serving/ui-oid-plugin.ts
5037
5050
  var NODE_MODULES_RE = /\/node_modules\//;
5038
- var TOOLING_SRC_RE = /\/packages\/(engine\/src|create-vgai-project|vgai-cli|editor-sdk)\//;
5051
+ var TOOLING_SRC_RE = /\/packages\/(engine\/src|create-volter-project|volter-cli|editor-sdk)\//;
5039
5052
  var EDITOR_SRC_RE = /\/packages\/editor\/src\//;
5040
5053
  var EDITABLE_COMPONENTS_RE = /\/ui-editor\/editable-components\/.*\.tsx$/;
5041
5054
  var VENDOR_RE = /\/vendor\//;
@@ -5092,7 +5105,7 @@ function sourceResourcePath(file, projectRoot) {
5092
5105
  const path = relative(projectRoot, file).replace(/\\/g, "/");
5093
5106
  if (path && path !== ".." && !path.startsWith("../") && !isAbsolute(path)) return path;
5094
5107
  const identity = createHash("sha256").update(file).digest("hex").slice(0, 16);
5095
- return `.vgai/external-source/${identity}/${basename(file)}`;
5108
+ return `.volter/external-source/${identity}/${basename(file)}`;
5096
5109
  }
5097
5110
  var utilityClassSupport = /* @__PURE__ */ new Map();
5098
5111
  function owningPackageDir(file, fallback) {
@@ -5117,7 +5130,7 @@ function utilityClassEvidence(dir) {
5117
5130
  const absolute = join2(dir, name);
5118
5131
  if (existsSync3(absolute)) read(absolute, name);
5119
5132
  }
5120
- const skip = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".vgai", ".claude"]);
5133
+ const skip = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".volter", ".claude"]);
5121
5134
  let budget = 80;
5122
5135
  const walk = (current, prefix, depth) => {
5123
5136
  if (depth > 3 || budget <= 0) return;
@@ -5241,7 +5254,7 @@ function rejectPendingPropRequests(error) {
5241
5254
  }
5242
5255
  function declaredPropWorker() {
5243
5256
  if (propWorker) return propWorker;
5244
- const worker = new Worker(componentPropWorkerUrl(), { name: "vgai-component-props" });
5257
+ const worker = new Worker(componentPropWorkerUrl(), { name: "volter-component-props" });
5245
5258
  worker.on(
5246
5259
  "message",
5247
5260
  (message) => {
@@ -5877,8 +5890,8 @@ function uiOidPlugin(services, include = defaultProjectScopeInclude) {
5877
5890
  try {
5878
5891
  const body = await readJson(req);
5879
5892
  const claimedParticipantId = body["participantId"];
5880
- const trustedParticipantId = req.headers["x-vgai-share-participant-id"];
5881
- const trustedRole = req.headers["x-vgai-share-role"];
5893
+ const trustedParticipantId = req.headers["x-volter-share-participant-id"];
5894
+ const trustedRole = req.headers["x-volter-share-role"];
5882
5895
  if (typeof trustedParticipantId === "string" && typeof claimedParticipantId === "string" && trustedParticipantId !== claimedParticipantId) {
5883
5896
  throw new Error(
5884
5897
  "The authenticated share participant does not match the source mutation author."
@@ -5943,10 +5956,10 @@ function uiOidPlugin(services, include = defaultProjectScopeInclude) {
5943
5956
  const index = currentOidIndex(store, activeProjectRoot(), server);
5944
5957
  res.setHeader("Server-Timing", `index;dur=${Date.now() - indexStarted}`);
5945
5958
  res.setHeader(
5946
- "X-Vgai-Oid-Index-Enriched",
5959
+ "X-Volter-Oid-Index-Enriched",
5947
5960
  index === lastEnrichedIndex?.index ? "1" : "0"
5948
5961
  );
5949
- if (typeof req.headers["x-vgai-share-participant-id"] !== "string") {
5962
+ if (typeof req.headers["x-volter-share-participant-id"] !== "string") {
5950
5963
  return json(res, index);
5951
5964
  }
5952
5965
  return json(
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@volter/editor-react",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "AGPL-3.0-only",
5
- "version": "0.5.67",
5
+ "version": "0.5.69",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -31,15 +31,15 @@
31
31
  "prepack": "npm run build",
32
32
  "typecheck": "tsc -p tsconfig.json"
33
33
  },
34
- "vgai": {
34
+ "volter": {
35
35
  "contributions": [
36
36
  "./contributions/component-contracts.service.ts"
37
37
  ],
38
38
  "serving": "./dist-node/serving.mjs"
39
39
  },
40
40
  "dependencies": {
41
- "@volter/editor-sdk": "0.5.67",
42
- "@volter/editor-project": "0.5.67",
41
+ "@volter/editor-sdk": "0.5.69",
42
+ "@volter/editor-project": "0.5.69",
43
43
  "typescript": "5.9.3"
44
44
  },
45
45
  "peerDependencies": {
package/serving/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@volter/editor-react`'s server half (`package.json#vgai.serving`, the project-serving door in
2
+ * `@volter/editor-react`'s server half (`package.json#volter.serving`, the project-serving door in
3
3
  * `@volter/editor-sdk/session/project-serving`): the JSX identity stamp and the `/__ui-source/*`
4
4
  * authoring routes over a project's React and React Three Fiber source, and the source answers
5
5
  * the kit's region decision, validation and Content index ask of a JSX lane.
@@ -19,13 +19,13 @@
19
19
  * dev-server `fs.allow` pipeline `server/dev.ts` already builds for project scripts
20
20
  * (T3.3) — so `ReactRootAuthoringAdapter` needs those files instrumented too.
21
21
  * `defaultProjectScopeInclude` widens to PROJECT scope, not repo-global: any `.tsx`
22
- * file outside `node_modules`, EXCEPT the vgai tooling/engine source trees this repo
23
- * itself is built from (`packages/engine/src`, `packages/create-vgai-project`,
24
- * `packages/vgai-cli`, `packages/editor-sdk`, and `packages/editor/src` generally —
22
+ * file outside `node_modules`, EXCEPT the volter tooling/engine source trees this repo
23
+ * itself is built from (`packages/engine/src`, `packages/create-volter-project`,
24
+ * `packages/volter-cli`, `packages/editor-sdk`, and `packages/editor/src` generally —
25
25
  * carving OUT `ui-editor/editable-components` so the existing UI-edit-mode surface
26
26
  * keeps working unchanged). This does NOT depend on which project happens to be
27
27
  * open — `fs.allow` already bounds what Vite can even reach, and this predicate
28
- * additionally keeps the vgai app's OWN react source un-instrumented.
28
+ * additionally keeps the volter app's OWN react source un-instrumented.
29
29
  *
30
30
  * Vendored trees: a vendored GAME's source is STAMPED; every other `/vendor/`
31
31
  * tree is not. `data-oid` stamping is what makes a `.tsx` file addressable —
@@ -53,11 +53,11 @@
53
53
  * the write-back ban becomes IDENTITY-scoped, not just location-scoped: the
54
54
  * `/vendor/` regex above only protects the repo's OWN vendored trees, but
55
55
  * wave 2 lets an `ingest-react` world live in ANY external, user-owned
56
- * folder (`vgai edit <folder>`) — an ingested-as-is foreign game there would
56
+ * folder (`volter-game-editor edit <folder>`) — an ingested-as-is foreign game there would
57
57
  * otherwise be silently stamped/writable, reopening the never-modify-game-
58
58
  * source rule by location rather than by identity. `nearestManifestExcludesIngestReact`
59
59
  * walks up from a candidate `.tsx`'s own directory to the NEAREST
60
- * `vgai.project.json`; if that manifest declares an `{ ingest }` react world,
60
+ * `volter.project.json`; if that manifest declares an `{ ingest }` react world,
61
61
  * the whole manifest folder's subtree is excluded — exact, not coarse:
62
62
  * `ingest-react` is single-world by construction (`binding-resolver.ts`'s
63
63
  * `MULTI_WORLD_ALLOWED_IDENTITIES` exclusion), so "the manifest folder" IS
@@ -146,7 +146,7 @@ import {
146
146
  export { applyStyleWriteRequest, oidAttributeForSurface };
147
147
 
148
148
  const NODE_MODULES_RE = /\/node_modules\//;
149
- const TOOLING_SRC_RE = /\/packages\/(engine\/src|create-vgai-project|vgai-cli|editor-sdk)\//;
149
+ const TOOLING_SRC_RE = /\/packages\/(engine\/src|create-volter-project|volter-cli|editor-sdk)\//;
150
150
  const EDITOR_SRC_RE = /\/packages\/editor\/src\//;
151
151
  const EDITABLE_COMPONENTS_RE = /\/ui-editor\/editable-components\/.*\.tsx$/;
152
152
  /** Track N, D-N4 item 2 — see the module doc comment's "Vendored-tree exclusion". */
@@ -170,13 +170,13 @@ const VENDOR_RE = /\/vendor\//;
170
170
  */
171
171
  const VENDORED_GAME_SRC_RE = /\/vendor\/games\/[^/]+\//;
172
172
 
173
- /** True if a parsed `vgai.project.json` body declares at least one
173
+ /** True if a parsed `volter.project.json` body declares at least one
174
174
  * React root with an `{ ingest }` adapter — the `{ ingest }` shape alone is
175
175
  * the answer, because every ingest-react root is D-N8 no-authoring.
176
176
  * Defensive against
177
177
  * any shape (never trusts the manifest is even an object) — this is a
178
178
  * best-effort identity probe for a WRITE-BACK BAN, not manifest validation
179
- * (the real Zod schema/loader is `@vgai/project/manifest/load`, not reachable
179
+ * (the real Zod schema/loader is `@volter/project/manifest/load`, not reachable
180
180
  * from this vite-config-time, dependency-light file by design). */
181
181
  function manifestDeclaresIngestReactWorld(parsed: unknown): boolean {
182
182
  if (!parsed || typeof parsed !== 'object') return false;
@@ -236,7 +236,7 @@ function nearestManifestExcludesIngestReact(dir: string): boolean {
236
236
  * Cap 2 (React visual-edit parity): the scope guard for the `/__ui-source/css` endpoint.
237
237
  * A CSS file path (from a stylesheet's `data-vite-dev-id`) is editable iff it is a
238
238
  * first-party PROJECT `.css` file — same carve-outs as `defaultProjectScopeInclude`
239
- * (never node_modules, vendored trees, the vgai tooling/engine source, or the editor's
239
+ * (never node_modules, vendored trees, the volter tooling/engine source, or the editor's
240
240
  * own source). Paths are normalized to forward slashes so a Windows dev-id matches. This
241
241
  * is the CSS analogue of the `.tsx` stamping scope: an out-of-scope path must not become
242
242
  * writable through a client-supplied `file`.
@@ -267,7 +267,7 @@ function sourceResourcePath(file: string, projectRoot: string): string {
267
267
  const path = relative(projectRoot, file).replace(/\\/g, '/');
268
268
  if (path && path !== '..' && !path.startsWith('../') && !isAbsolute(path)) return path;
269
269
  const identity = createHash('sha256').update(file).digest('hex').slice(0, 16);
270
- return `.vgai/external-source/${identity}/${basename(file)}`;
270
+ return `.volter/external-source/${identity}/${basename(file)}`;
271
271
  }
272
272
 
273
273
  /**
@@ -312,7 +312,7 @@ function utilityClassEvidence(dir: string): Map<string, string> {
312
312
  }
313
313
  // Bounded walk for the css/html entry markers (`@tailwind`, a CDN script):
314
314
  // shallow, skipping the directories that never hold a project's own entry.
315
- const skip = new Set(['node_modules', 'dist', 'build', 'coverage', '.git', '.vgai', '.claude']);
315
+ const skip = new Set(['node_modules', 'dist', 'build', 'coverage', '.git', '.volter', '.claude']);
316
316
  let budget = 80;
317
317
  const walk = (current: string, prefix: string, depth: number): void => {
318
318
  if (depth > 3 || budget <= 0) return;
@@ -530,7 +530,7 @@ function rejectPendingPropRequests(error: Error): void {
530
530
 
531
531
  function declaredPropWorker(): Worker {
532
532
  if (propWorker) return propWorker;
533
- const worker = new Worker(componentPropWorkerUrl(), { name: 'vgai-component-props' });
533
+ const worker = new Worker(componentPropWorkerUrl(), { name: 'volter-component-props' });
534
534
  worker.on(
535
535
  'message',
536
536
  (message: { id?: unknown; props?: Record<string, ComponentPropSpec[]>; error?: unknown }) => {
@@ -1549,7 +1549,7 @@ export function uiOidPlugin(
1549
1549
  transform(code, id) {
1550
1550
  // A VIRTUAL MODULE IS NOT A FILE, and everything below this line treats
1551
1551
  // `clean` as one — `resolveOidSurface` walks up from it looking for the
1552
- // owning `vgai.project.json`, and `transformSource` records it as the
1552
+ // owning `volter.project.json`, and `transformSource` records it as the
1553
1553
  // stamped file's path. Vite's convention is that a `\0`-prefixed id was
1554
1554
  // synthesized by a plugin and has no path behind it, so a path plugin
1555
1555
  // skips it.
@@ -1566,7 +1566,7 @@ export function uiOidPlugin(
1566
1566
  // W1 — R3F-dialect files get ` userData-oid` (fiber pierces it into
1567
1567
  // `object.userData.oid`); react-dom files keep ` data-oid` unchanged.
1568
1568
  // WHICH REGION owns this exact file decides: a declaration the project
1569
- // made (an `include` glob or a `mounts` entry in `vgai.adapter.ts`), the
1569
+ // made (an `include` glob or a `mounts` entry in `volter.adapter.ts`), the
1570
1570
  // manifest root whose `entry` it is, or reach from one through the live
1571
1571
  // import graph. Nothing is inferred from the file's own bytes; a file
1572
1572
  // nothing places gets the documented `data-oid` default, said out loud.
@@ -1688,8 +1688,8 @@ export function uiOidPlugin(
1688
1688
  try {
1689
1689
  const body = await readJson(req);
1690
1690
  const claimedParticipantId = body['participantId'];
1691
- const trustedParticipantId = req.headers['x-vgai-share-participant-id'];
1692
- const trustedRole = req.headers['x-vgai-share-role'];
1691
+ const trustedParticipantId = req.headers['x-volter-share-participant-id'];
1692
+ const trustedRole = req.headers['x-volter-share-role'];
1693
1693
  if (
1694
1694
  typeof trustedParticipantId === 'string' &&
1695
1695
  typeof claimedParticipantId === 'string' &&
@@ -1792,10 +1792,10 @@ export function uiOidPlugin(
1792
1792
  const index = currentOidIndex(store, activeProjectRoot(), server);
1793
1793
  res.setHeader('Server-Timing', `index;dur=${Date.now() - indexStarted}`);
1794
1794
  res.setHeader(
1795
- 'X-Vgai-Oid-Index-Enriched',
1795
+ 'X-Volter-Oid-Index-Enriched',
1796
1796
  index === lastEnrichedIndex?.index ? '1' : '0',
1797
1797
  );
1798
- if (typeof req.headers['x-vgai-share-participant-id'] !== 'string') {
1798
+ if (typeof req.headers['x-volter-share-participant-id'] !== 'string') {
1799
1799
  return json(res, index);
1800
1800
  }
1801
1801
  return json(
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * OID instrumentation (C1/C2) — the visual-edit "stamp every JSX element with a
3
- * stable source id" trick, ported to vgai using the TypeScript compiler API
4
- * (vgai has `typescript`; it does NOT have @babel/*). Hand-authored React UI
3
+ * stable source id" trick, ported to volter using the TypeScript compiler API
4
+ * (volter has `typescript`; it does NOT have @babel/*). Hand-authored React UI
5
5
  * component source is transformed at build/dev time to carry a `data-oid` on every
6
6
  * JSX element, and an OID -> {file,line,col,component,tag} index is built so the
7
7
  * editor can map a clicked element back to its exact source location.
@@ -168,7 +168,7 @@ interface SourceEdit {
168
168
  const R3F_TRANSFORM_PROPS = ['position', 'rotation', 'scale'] as const;
169
169
 
170
170
  /**
171
- * Contracts for components vgai does NOT own, keyed by package specifier and
171
+ * Contracts for components volter does NOT own, keyed by package specifier and
172
172
  * export name. The project resolver reads a local component's contract out of
173
173
  * its own source; an installed package has no source to walk from the browser
174
174
  * tier and no reason to expect one, so the few whose native root is part of the
@@ -1067,7 +1067,7 @@ export function sourceProvesR3f(evidence: SourceDialectEvidence): boolean {
1067
1067
  * the HMR half of this same decision, so the two cannot drift apart by hand.
1068
1068
  *
1069
1069
  * 2. **The DEFAULT.** When `surface` is `undefined` — no region placed the
1070
- * file (an ad-hoc file with no `vgai.project.json` above it, or a module no
1070
+ * file (an ad-hoc file with no `volter.project.json` above it, or a module no
1071
1071
  * region's import closure reaches and none declares) — the answer is
1072
1072
  * `data-oid`, the documented default, and the CALLER says so out loud
1073
1073
  * (`server/project-root-surface.ts`'s `OID001`/`OID002`). There is
@@ -1075,7 +1075,7 @@ export function sourceProvesR3f(evidence: SourceDialectEvidence): boolean {
1075
1075
  * for a file it could not place, silently, and being wrong that way is
1076
1076
  * indistinguishable from being right. Zero inference (ARCHITECTURE-CORE
1077
1077
  * §The editor protocol) makes the fix a declaration — an `include` glob or
1078
- * a `mounts` entry in `vgai.adapter.ts` — not a better heuristic.
1078
+ * a `mounts` entry in `volter.adapter.ts` — not a better heuristic.
1079
1079
  *
1080
1080
  * Nothing about a file's own bytes outranks the region — not even a file that
1081
1081
  * renders THREE host elements while its region says `dom`. That disagreement is
@@ -1209,7 +1209,7 @@ function dreiHtmlLocalNames(sf: ts.SourceFile): Set<string> {
1209
1209
  * `r3f-project-contracts.ts`'s `resolveRelativeModule` already draws between
1210
1210
  * "source this editor walks" and "a dependency it never guesses about".
1211
1211
  *
1212
- * Why the callsite stamp must skip them: `__vgaiOid`/`__vgaiLabel` are a
1212
+ * Why the callsite stamp must skip them: `__volterOid`/`__volterLabel` are a
1213
1213
  * CONTRACT BETWEEN TWO HALVES OF THIS TRANSFORM. The callsite half writes
1214
1214
  * them; the only thing that ever reads them is the callee half —
1215
1215
  * `componentInstanceExpressions` destructuring them out of the component's own
@@ -1223,8 +1223,8 @@ function dreiHtmlLocalNames(sf: ts.SourceFile): Set<string> {
1223
1223
  * MIXED file (an R3F `Scene` and a DOM `Hud` in one module, so the file's one
1224
1224
  * dialect is `userData-oid`), and its `<Canvas>` — `@react-three/fiber`'s own,
1225
1225
  * which spreads its rest props onto the `<div>` it wraps the canvas in — put
1226
- * `__vgaiOid` and `__vgaiLabel` straight into the DOM, for two permanent
1227
- * `React does not recognize the \`__vgaiOid\` prop on a DOM element` errors on
1226
+ * `__volterOid` and `__volterLabel` straight into the DOM, for two permanent
1227
+ * `React does not recognize the \`__volterOid\` prop on a DOM element` errors on
1228
1228
  * every mount of a real game. Nothing was lost by not stamping it, because
1229
1229
  * nothing was reading it.
1230
1230
  *
@@ -1285,7 +1285,7 @@ function dreiCameraLocalNames(sf: ts.SourceFile): Set<string> {
1285
1285
  * tag's callee is unknowable at transform time, and the same hazard as
1286
1286
  * {@link packageImportedLocalNames} for exactly the same reason.
1287
1287
  *
1288
- * `__vgaiOid`/`__vgaiLabel` are a contract between two halves of this
1288
+ * `__volterOid`/`__volterLabel` are a contract between two halves of this
1289
1289
  * transform: the callsite half writes them, the callee half destructures them
1290
1290
  * out of the component's own parameters. A tag bound to a parameter names
1291
1291
  * WHATEVER the caller passed, so this module cannot know whether the callee
@@ -1297,7 +1297,7 @@ function dreiCameraLocalNames(sf: ts.SourceFile): Set<string> {
1297
1297
  * is a parameter — drei's `Stats`/`OrbitControls`, cannon's `Debug`, or a
1298
1298
  * project component, depending on the call) and every R3F `*.stories.tsx`
1299
1299
  * decorator's `<Story />`. Two standing
1300
- * `React does not recognize the \`__vgaiOid\` prop on a DOM element` errors on
1300
+ * `React does not recognize the \`__volterOid\` prop on a DOM element` errors on
1301
1301
  * every run of the acceptance portfolio.
1302
1302
  *
1303
1303
  * Nothing is lost by not stamping, and identity actually IMPROVES: a forwarder
@@ -1437,42 +1437,42 @@ export function transformSource(
1437
1437
  // Default the whole binding. Isolation/preview/bake call a stamped
1438
1438
  // export as a factory (`MainScene()` / `WOOD()`) with no args; a
1439
1439
  // required destructure of `undefined` is `Cannot destructure property
1440
- // '__vgaiOid'`. React still passes `{}` at a real callsite.
1441
- sourceEdits.push({ pos: parameters.pos, text: '{ __vgaiOid, __vgaiLabel } = {}' });
1442
- return { instanceExpression: '__vgaiOid', labelExpression: '__vgaiLabel' };
1440
+ // '__volterOid'`. React still passes `{}` at a real callsite.
1441
+ sourceEdits.push({ pos: parameters.pos, text: '{ __volterOid, __volterLabel } = {}' });
1442
+ return { instanceExpression: '__volterOid', labelExpression: '__volterLabel' };
1443
1443
  }
1444
1444
  if (ts.isObjectBindingPattern(first.name)) {
1445
1445
  sourceEdits.push({
1446
1446
  pos: first.name.getStart(sf) + 1,
1447
- text: '__vgaiOid, __vgaiLabel, ',
1447
+ text: '__volterOid, __volterLabel, ',
1448
1448
  });
1449
1449
  // After the WHOLE parameter, including a type annotation:
1450
- // `({ name, ...props }: T)` must become `({ __vgaiOid, …, name, ...props }: T = {})`.
1450
+ // `({ name, ...props }: T)` must become `({ __volterOid, …, name, ...props }: T = {})`.
1451
1451
  // Inserting at the binding's end produced `} = {}: T`, which is not
1452
1452
  // TypeScript and broke every typed scene export the look verb imports.
1453
1453
  if (!first.initializer) {
1454
1454
  sourceEdits.push({ pos: first.getEnd(), text: ' = {}' });
1455
1455
  }
1456
- return { instanceExpression: '__vgaiOid', labelExpression: '__vgaiLabel' };
1456
+ return { instanceExpression: '__volterOid', labelExpression: '__volterLabel' };
1457
1457
  }
1458
1458
  if (ts.isIdentifier(first.name) && !first.dotDotDotToken) {
1459
1459
  // Preserve the authored identifier as the REST binding. Code below the
1460
1460
  // parameter continues to read `props`, but it now sees only game props:
1461
1461
  // `function Card(props: Props)` becomes
1462
- // `function Card({ __vgaiOid, __vgaiLabel, ...props }: Props)`.
1462
+ // `function Card({ __volterOid, __volterLabel, ...props }: Props)`.
1463
1463
  // Remove an optional marker while replacing the identifier; a binding
1464
1464
  // pattern cannot itself be optional, so its equivalent is a `{}` default.
1465
1465
  sourceEdits.push({
1466
1466
  pos: first.name.getStart(sf),
1467
1467
  end: first.questionToken?.end ?? first.name.getEnd(),
1468
- text: `{ __vgaiOid, __vgaiLabel, ...${first.name.text} }`,
1468
+ text: `{ __volterOid, __volterLabel, ...${first.name.text} }`,
1469
1469
  });
1470
1470
  if (!first.initializer) {
1471
1471
  sourceEdits.push({ pos: first.getEnd(), text: ' = {}' });
1472
1472
  }
1473
- return { instanceExpression: '__vgaiOid', labelExpression: '__vgaiLabel' };
1473
+ return { instanceExpression: '__volterOid', labelExpression: '__volterLabel' };
1474
1474
  }
1475
- return { instanceExpression: '__vgaiOid', labelExpression: '__vgaiLabel' };
1475
+ return { instanceExpression: '__volterOid', labelExpression: '__volterLabel' };
1476
1476
  };
1477
1477
 
1478
1478
  const pushComponent = (
@@ -1584,7 +1584,7 @@ export function transformSource(
1584
1584
  const element = ts.isJsxOpeningElement(node) ? node.parent : node;
1585
1585
  if (ts.isJsxElement(element) || ts.isJsxSelfClosingElement(element)) {
1586
1586
  // An installed package's module never passes through this transform,
1587
- // so the callee half of the `__vgaiOid` contract never runs for it and
1587
+ // so the callee half of the `__volterOid` contract never runs for it and
1588
1588
  // the props can only escape (into the DOM, via any package component
1589
1589
  // that spreads its rest props onto a host element). The callsite is
1590
1590
  // still INDEXED above — the source entry, its parent and its contract
@@ -1619,11 +1619,11 @@ export function transformSource(
1619
1619
  : tag;
1620
1620
  const labelAttribute =
1621
1621
  authoredName?.initializer && ts.isJsxExpression(authoredName.initializer)
1622
- ? `__vgaiLabel={${authoredName.initializer.expression?.getText(sf) ?? JSON.stringify(tag)}}`
1623
- : `__vgaiLabel=${JSON.stringify(label)}`;
1622
+ ? `__volterLabel={${authoredName.initializer.expression?.getText(sf) ?? JSON.stringify(tag)}}`
1623
+ : `__volterLabel=${JSON.stringify(label)}`;
1624
1624
  sourceEdits.push({
1625
1625
  pos: node.attributes.end,
1626
- text: ` __vgaiOid="${oid}" ${labelAttribute}`,
1626
+ text: ` __volterOid="${oid}" ${labelAttribute}`,
1627
1627
  });
1628
1628
  }
1629
1629
  } else {
@@ -188,7 +188,7 @@ export function planDeleteStory(source: string, name: string): CsfStoryPlan {
188
188
  return {
189
189
  ok: false,
190
190
  reason:
191
- `'${name}' is this file's declared defaultStory — point vgai.defaultStory at another ` +
191
+ `'${name}' is this file's declared defaultStory — point volter.defaultStory at another ` +
192
192
  'story first',
193
193
  };
194
194
  }
@@ -385,7 +385,7 @@ export default meta;
385
385
  type Story = StoryObj<typeof meta>;
386
386
 
387
387
  export const Default: Story = {
388
- parameters: { vgai: { default: true } },
388
+ parameters: { volter: { default: true } },
389
389
  };
390
390
  `;
391
391
 
@@ -349,5 +349,8 @@ export function planSourceEdit(
349
349
  request.ensureImport.kind,
350
350
  ).code
351
351
  : result.code;
352
- return planned(entry.file, src, code, { changed: result.changed });
352
+ return planned(entry.file, src, code, {
353
+ changed: result.changed,
354
+ ...(result.error ? { error: result.error } : {}),
355
+ });
353
356
  }
@@ -59,7 +59,7 @@
59
59
  * PURELY ADDITIVE. The channel these diagnostics have always had is untouched:
60
60
  * `server/project-validation.ts`'s `validateSource` still formats every one of
61
61
  * them into the dev server's terminal warning block and into the project
62
- * warnings map that `/__editor/state` (and so `vgai status`) reports. Rows are
62
+ * warnings map that `/__editor/state` (and so `volter-game-editor status`) reports. Rows are
63
63
  * a second, better-placed audience, not a replacement.
64
64
  */
65
65
 
@@ -1,6 +1,6 @@
1
1
  /** Exact source tokens for native R3F scene attachments.
2
2
  *
3
- * These are not a VGAI environment format. `<color attach="background">`,
3
+ * These are not a VOLTER environment format. `<color attach="background">`,
4
4
  * `<fog attach="fog">`, and `<fogExp2 attach="fog">` are ordinary Fiber
5
5
  * projections of THREE.Scene properties. The live Scene remains runtime truth;
6
6
  * this metadata only identifies literal constructor arguments that can be
@@ -2,10 +2,10 @@
2
2
  * Project-local R3F component contract discovery.
3
3
  *
4
4
  * This deliberately follows ordinary TypeScript/React modules instead of
5
- * asking game source to register components with vgai. Relative imports,
5
+ * asking game source to register components with volter. Relative imports,
6
6
  * named/default exports, aliases, and re-exports are enough for the editor to
7
7
  * understand the shared-component shape coding models normally produce.
8
- * External packages remain conservative: vgai never guesses at their runtime
8
+ * External packages remain conservative: volter never guesses at their runtime
9
9
  * scene ownership.
10
10
  */
11
11
  import { existsSync, readFileSync, statSync } from 'node:fs';
@@ -419,7 +419,7 @@ export function r3fAuthoringDiagnostics(
419
419
  if (
420
420
  /^[A-Z]/.test(tag) &&
421
421
  contract?.root === 'single' &&
422
- // A package component vgai knows only through the built-in table is not
422
+ // A package component volter knows only through the built-in table is not
423
423
  // a hierarchy row of its own — `<RigidBody>` never receives the editor's
424
424
  // stamp, so its props are attributed to the node it wraps
425
425
  // (`collapsedWrappersOf`). Naming it would name nothing.
@@ -1565,6 +1565,8 @@ export function removePropAttribute(
1565
1565
  export interface StructEditResult {
1566
1566
  code: string;
1567
1567
  changed: boolean;
1568
+ /** Why an op that could not apply refused, when it can say. */
1569
+ error?: string;
1568
1570
  }
1569
1571
 
1570
1572
  /** Expand a JSX element at `elementStart` to its full LINE block (leading whitespace on the
@@ -1809,6 +1811,19 @@ export function insertSiblingElement(
1809
1811
  ): StructEditResult {
1810
1812
  const trimmed = snippet.trim();
1811
1813
  if (!trimmed.startsWith('<')) return { code, changed: false };
1814
+ // A sibling is legal only beside a JSX CHILD: an element whose text is preceded by a tag's `>`
1815
+ // (not an arrow's `=>`) or an expression child's `}`. The lone element a component returns has no JSX parent, and a
1816
+ // second element beside it is a syntax error, never an insert.
1817
+ let before = elementStart - 1;
1818
+ while (before >= 0 && /\s/.test(code[before]!)) before--;
1819
+ const arrowBody = code[before] === '>' && code[before - 1] === '=';
1820
+ if (before < 0 || arrowBody || (code[before] !== '>' && code[before] !== '}')) {
1821
+ return {
1822
+ code,
1823
+ changed: false,
1824
+ error: 'this element is not a JSX child (a lone returned element), so it can have no sibling.',
1825
+ };
1826
+ }
1812
1827
  const r = lineBlockRange(code, elementStart);
1813
1828
  if (!r) return { code, changed: false };
1814
1829
  const indent = code.slice(r.lineStart, elementStart).match(/^(\s*)/)?.[1] ?? '';