@volter/editor-threejs 0.5.210 → 0.5.212

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.
Files changed (133) hide show
  1. package/NOTICE +7 -2
  2. package/README.md +8 -13
  3. package/contributions/animation-mixers.service.ts +2 -4
  4. package/contributions/animation-timeline.utility.tsx +1 -2
  5. package/contributions/three.page.ts +50 -0
  6. package/dist-node/serving.mjs +84 -3
  7. package/dist-node/shared-three.mjs +145 -0
  8. package/package.json +296 -16
  9. package/serving/animation-stamp.ts +1 -1
  10. package/serving/index.ts +22 -5
  11. package/serving/model-import-conversion.ts +6 -3
  12. package/serving/shared-three.ts +176 -0
  13. package/src/adapter/constraint.ts +1 -18
  14. package/src/adapter/object3d-authoring-subject.ts +1 -12
  15. package/src/adapter/reflection-probe.ts +1 -9
  16. package/src/adapter/three-contract.ts +6 -60
  17. package/src/animation/live-mixers.ts +0 -4
  18. package/src/api.ts +7 -0
  19. package/src/asset-loaders.ts +2 -178
  20. package/src/capture/photograph-sampling.ts +333 -0
  21. package/src/capture/scene.ts +1 -1
  22. package/src/ecs/object-marks.ts +6 -14
  23. package/src/ecs/user-data.ts +3 -138
  24. package/src/kit/animation/three-clips-subject.ts +19 -42
  25. package/src/kit/asset-compare-core.ts +161 -0
  26. package/src/kit/asset-compare.ts +8 -27
  27. package/src/kit/asset-preview-command.ts +14 -19
  28. package/src/kit/asset-preview-framing.ts +8 -67
  29. package/src/kit/asset-preview.ts +109 -342
  30. package/src/kit/asset-workflow/model-inspection.ts +11 -154
  31. package/src/kit/asset-workflow/preview-resource-lifetime.ts +25 -0
  32. package/src/kit/authoring/component-instance-root.ts +3 -39
  33. package/src/kit/authoring/live-object-transform.ts +1 -30
  34. package/src/kit/authoring/object3d-document-session-registry.ts +7 -15
  35. package/src/kit/authoring/object3d-document-session.ts +471 -208
  36. package/src/kit/authoring/shell-viewport-policy.ts +18 -24
  37. package/src/kit/authoring/source-object3d-authoring-adapter.ts +6 -48
  38. package/src/kit/authoring/three-projection-core.ts +11 -40
  39. package/src/kit/authoring/viewport-pick-context.ts +5 -12
  40. package/src/kit/authoring/viewport-raycast.ts +15 -93
  41. package/src/kit/authoring/world-hidden-viewport.ts +8 -61
  42. package/src/kit/capture-camera-pose.ts +68 -0
  43. package/src/kit/collaboration-presence.ts +41 -0
  44. package/src/kit/components/CameraInfo.tsx +4 -6
  45. package/src/kit/components/InspectorObjectPreview.tsx +3 -30
  46. package/src/kit/components/Object3DDocumentToolbar.css +75 -0
  47. package/src/kit/components/Object3DDocumentToolbar.tsx +18 -84
  48. package/src/kit/components/Object3DDocumentViewport.tsx +5 -8
  49. package/src/kit/components/StageHost.css +40 -0
  50. package/src/kit/components/StageHost.tsx +181 -404
  51. package/src/kit/components/StageOverlays.tsx +1 -2
  52. package/src/kit/components/StatsOverlay.tsx +5 -17
  53. package/src/kit/components/ViewportFurniture.tsx +27 -154
  54. package/src/kit/components/ViewportOverlay.tsx +13 -17
  55. package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
  56. package/src/kit/components/ViewportShadingMenu.tsx +2 -42
  57. package/src/kit/components/ViewportViewMenu.tsx +7 -18
  58. package/src/kit/components/asset-viewers/EntityModelDocument.tsx +4 -13
  59. package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +5 -5
  60. package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +24 -102
  61. package/src/kit/components/asset-viewers/LutAssetDocument.tsx +6 -6
  62. package/src/kit/components/asset-viewers/Object3DPreview.tsx +10 -45
  63. package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +2 -2
  64. package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +11 -10
  65. package/src/kit/components/asset-viewers/shader-source.ts +144 -0
  66. package/src/kit/components/asset-viewers/three-asset-viewers.tsx +2 -14
  67. package/src/kit/components/object3d-contribution-surfaces.tsx +1 -1
  68. package/src/kit/components/stage-keyboard.tsx +6 -12
  69. package/src/kit/components/stage-overlay-set.tsx +11 -20
  70. package/src/kit/components/stage-presence-markers.ts +9 -32
  71. package/src/kit/components/stage-transform-chrome.ts +4 -4
  72. package/src/kit/components/stage-transform-tools.tsx +7 -14
  73. package/src/kit/components/standard-viewport-dressing.ts +47 -167
  74. package/src/kit/components/world-root-binding.ts +2 -7
  75. package/src/kit/constraint-helper.ts +1 -1
  76. package/src/kit/document-renderer-session.ts +114 -0
  77. package/src/kit/editor-presentation-activity.ts +58 -0
  78. package/src/kit/editor-shell-store.ts +31 -403
  79. package/src/kit/editor-viewport.ts +476 -1762
  80. package/src/kit/entity-lod.ts +3 -16
  81. package/src/kit/entity-object.ts +1 -39
  82. package/src/kit/hierarchy-mark-reader.ts +13 -30
  83. package/src/kit/instanced-presentation.ts +1 -47
  84. package/src/kit/live-gesture-lock.ts +35 -0
  85. package/src/kit/live-module-source.ts +12 -52
  86. package/src/kit/model-thumbnail.ts +7 -35
  87. package/src/kit/play-camera-flight.ts +7 -16
  88. package/src/kit/projection/three.ts +13 -366
  89. package/src/kit/retained-document-states.ts +91 -0
  90. package/src/kit/scene-document-viewport.ts +3 -3
  91. package/src/kit/scene-framing.ts +11 -152
  92. package/src/kit/scene-view-fog.ts +2 -47
  93. package/src/kit/spatial-handle-visuals.ts +19 -28
  94. package/src/kit/stories/three-story-model.ts +5 -33
  95. package/src/kit/three-canvas-render.ts +3 -4
  96. package/src/kit/three-hierarchy-row-media.ts +3 -11
  97. package/src/kit/three-inspection-media.ts +6 -6
  98. package/src/kit/three-integration.ts +18 -18
  99. package/src/kit/three-state.ts +2 -19
  100. package/src/kit/three-viewport/camera-fit.ts +2 -6
  101. package/src/kit/three-viewport/selection-brackets.ts +5 -20
  102. package/src/kit/three-viewport/selection-outline.ts +4 -56
  103. package/src/kit/three-viewport/skeleton-helper.ts +1 -32
  104. package/src/kit/three-viewport/source-color.ts +1 -33
  105. package/src/kit/three-viewport/studio-environment.ts +4 -55
  106. package/src/kit/three-viewport-presentation.ts +23 -0
  107. package/src/kit/transport-strip.tsx +147 -0
  108. package/src/kit/viewport-actions.ts +8 -50
  109. package/src/kit/viewport-authoring-policy.ts +5 -52
  110. package/src/kit/viewport-commands.ts +28 -57
  111. package/src/kit/viewport-hotkeys.ts +29 -31
  112. package/src/kit/viewport-status-facet.ts +4 -5
  113. package/src/kit/viewport-surface-status.tsx +44 -0
  114. package/src/loader.ts +8 -71
  115. package/src/object3d-contributions.ts +18 -34
  116. package/src/viewport/clip-planes.ts +4 -32
  117. package/src/viewport/content-bounds.ts +13 -142
  118. package/src/viewport/editor-layers.ts +4 -26
  119. package/src/viewport/environment.ts +1 -1
  120. package/src/viewport/preview-renderer.ts +7 -45
  121. package/src/viewport/renderer-ownership.ts +14 -62
  122. package/src/viewport-api.ts +14 -33
  123. package/src/viewport-door.ts +16 -86
  124. package/contributions/three-integration.service.ts +0 -13
  125. package/src/adapter/hierarchy-marks.ts +0 -156
  126. package/src/adapter/ingest/scene-capture.ts +0 -843
  127. package/src/adapter/ingest/structural-ids.ts +0 -139
  128. package/src/adapter/ingest/visible-capture-window.ts +0 -302
  129. package/src/adapter/renderer-config.ts +0 -115
  130. package/src/asset-parse-error.ts +0 -29
  131. package/src/host-hierarchy-objects.ts +0 -31
  132. package/src/kit/authoring/design-time-settle.ts +0 -343
  133. package/src/kit/camera-authoring.ts +0 -175
@@ -0,0 +1,144 @@
1
+ /** Native GLSL document helpers. GLSL remains the artifact; these functions
2
+ * only describe it for Asset Lab and never serialize a Volter shader shape. */
3
+
4
+ export type ShaderStage = 'vertex' | 'fragment';
5
+
6
+ export interface ShaderUniformDescriptor {
7
+ readonly name: string;
8
+ readonly type: string;
9
+ readonly arraySize?: number | undefined;
10
+ readonly stages: readonly ShaderStage[];
11
+ }
12
+
13
+ export interface ShaderDiagnostic {
14
+ readonly stage: ShaderStage | 'program';
15
+ readonly severity: 'error' | 'warning';
16
+ readonly message: string;
17
+ readonly line?: number | undefined;
18
+ readonly column?: number | undefined;
19
+ }
20
+
21
+ const VERTEX_SUFFIXES = ['.vert', '.vert.glsl'] as const;
22
+ const FRAGMENT_SUFFIXES = ['.frag', '.frag.glsl'] as const;
23
+
24
+ function cleanPath(path: string): string {
25
+ return path.split(/[?#]/, 1)[0]!.toLowerCase();
26
+ }
27
+
28
+ export function shaderStageFromPath(path: string, source = ''): ShaderStage {
29
+ const clean = cleanPath(path);
30
+ if (VERTEX_SUFFIXES.some((suffix) => clean.endsWith(suffix))) return 'vertex';
31
+ if (FRAGMENT_SUFFIXES.some((suffix) => clean.endsWith(suffix))) return 'fragment';
32
+ return /\bgl_Position\b/.test(source) ? 'vertex' : 'fragment';
33
+ }
34
+
35
+ /** Conventional sibling only. A standalone `.glsl` file stays standalone. */
36
+ export function shaderCompanionPath(path: string): string | null {
37
+ const clean = path.split(/[?#]/, 1)[0]!;
38
+ if (/\.vert\.glsl$/i.test(clean)) return clean.replace(/\.vert\.glsl$/i, '.frag.glsl');
39
+ if (/\.frag\.glsl$/i.test(clean)) return clean.replace(/\.frag\.glsl$/i, '.vert.glsl');
40
+ if (/\.vert$/i.test(clean)) return clean.replace(/\.vert$/i, '.frag');
41
+ if (/\.frag$/i.test(clean)) return clean.replace(/\.frag$/i, '.vert');
42
+ return null;
43
+ }
44
+
45
+ function withoutComments(source: string): string {
46
+ // Preserve newlines so reflection never perturbs source-line diagnostics.
47
+ return source
48
+ .replace(/\/\*[\s\S]*?\*\//g, (comment) => comment.replace(/[^\n]/g, ' '))
49
+ .replace(/\/\/[^\n]*/g, '');
50
+ }
51
+
52
+ function declarations(source: string, stage: ShaderStage): ShaderUniformDescriptor[] {
53
+ const result: ShaderUniformDescriptor[] = [];
54
+ const pattern =
55
+ /\buniform\s+(?:(?:lowp|mediump|highp)\s+)?([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*(?:\[\s*(\d+)\s*\])?\s*;/g;
56
+ for (const match of withoutComments(source).matchAll(pattern)) {
57
+ const type = match[1];
58
+ const name = match[2];
59
+ if (!type || !name) continue;
60
+ const arraySize = match[3] ? Number.parseInt(match[3], 10) : undefined;
61
+ result.push({ name, type, ...(arraySize ? { arraySize } : {}), stages: [stage] });
62
+ }
63
+ return result;
64
+ }
65
+
66
+ export function reflectShaderUniforms(
67
+ vertexSource: string,
68
+ fragmentSource: string,
69
+ ): ShaderUniformDescriptor[] {
70
+ const reflected = new Map<string, ShaderUniformDescriptor>();
71
+ for (const uniform of [
72
+ ...declarations(vertexSource, 'vertex'),
73
+ ...declarations(fragmentSource, 'fragment'),
74
+ ]) {
75
+ const current = reflected.get(uniform.name);
76
+ if (!current) {
77
+ reflected.set(uniform.name, uniform);
78
+ continue;
79
+ }
80
+ reflected.set(uniform.name, {
81
+ ...current,
82
+ stages: [...new Set([...current.stages, ...uniform.stages])],
83
+ });
84
+ }
85
+ return [...reflected.values()];
86
+ }
87
+
88
+ export function parseShaderDiagnostics(
89
+ log: string,
90
+ stage: ShaderDiagnostic['stage'],
91
+ ): ShaderDiagnostic[] {
92
+ const diagnostics = log
93
+ .split('\n')
94
+ .map((line) => parseDiagnosticLine(line.trim(), stage))
95
+ .filter((diagnostic): diagnostic is ShaderDiagnostic => diagnostic !== null);
96
+ if (diagnostics.length === 0 && log.trim()) {
97
+ diagnostics.push({ stage, severity: 'error', message: log.trim() });
98
+ }
99
+ return diagnostics;
100
+ }
101
+
102
+ function parseDiagnosticLine(
103
+ text: string,
104
+ stage: ShaderDiagnostic['stage'],
105
+ ): ShaderDiagnostic | null {
106
+ if (!text) return null;
107
+ const colon = text.match(/^(ERROR|WARNING):\s*\d+:(\d+)(?::|\((\d+)\):)\s*(.*)$/i);
108
+ if (colon) {
109
+ return {
110
+ stage,
111
+ severity: colon[1]!.toLowerCase() === 'warning' ? 'warning' : 'error',
112
+ line: Number.parseInt(colon[2]!, 10),
113
+ ...(colon[3] ? { column: Number.parseInt(colon[3], 10) } : {}),
114
+ message: colon[4] || text,
115
+ };
116
+ }
117
+ const paren = text.match(/^\d+:(\d+)\((\d+)\):\s*(?:(error|warning)\s*:?)?\s*(.*)$/i);
118
+ if (!paren) return null;
119
+ return {
120
+ stage,
121
+ severity: paren[3]?.toLowerCase() === 'warning' ? 'warning' : 'error',
122
+ line: Number.parseInt(paren[1]!, 10),
123
+ column: Number.parseInt(paren[2]!, 10),
124
+ message: paren[4] || text,
125
+ };
126
+ }
127
+
128
+ /** Reset driver-reported lines after Three's injected ShaderMaterial prefix. */
129
+ export function previewShaderSource(source: string): string {
130
+ return `#line 1\n${source}`;
131
+ }
132
+
133
+ export const FALLBACK_VERTEX_SHADER = `varying vec2 vUv;
134
+
135
+ void main() {
136
+ vUv = uv;
137
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
138
+ }`;
139
+
140
+ export const FALLBACK_FRAGMENT_SHADER = `varying vec2 vUv;
141
+
142
+ void main() {
143
+ gl_FragColor = vec4(vUv, 0.55, 1.0);
144
+ }`;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * THE THREE.JS ASSET LAB VIEWERS, registered by route
3
- * (`@volter/sdk/kit/asset-viewers`): a glTF model, an environment map, a
3
+ * (`@volter/sdk/asset-viewers`): a glTF model, an environment map, a
4
4
  * LUT, a shader, a live modeling module and a live entity's model. Each loads on
5
5
  * first use, so a document that never opens one never pays for three.js.
6
6
  */
@@ -10,7 +10,7 @@ import {
10
10
  type JsonContentViewer,
11
11
  registerAssetViewer,
12
12
  registerJsonContentViewer,
13
- } from '@volter/sdk/kit/asset-viewers';
13
+ } from '@volter/sdk/asset-viewers';
14
14
  import { type ComponentType, lazy } from 'react';
15
15
 
16
16
  const viewers: Readonly<Record<AssetViewerRoute, ComponentType<AssetViewerProps>>> = {
@@ -64,18 +64,6 @@ const viewers: Readonly<Record<AssetViewerRoute, ComponentType<AssetViewerProps>
64
64
  ),
65
65
  };
66
66
  }),
67
- 'model-preview': lazy(async () => {
68
- const { Object3DPreview } = await import('./Object3DPreview');
69
- return {
70
- default: (props: AssetViewerProps) => (
71
- <Object3DPreview
72
- assetPath={props.assetPath}
73
- {...(props.materialPath ? { materialPath: props.materialPath } : {})}
74
- displayName={props.displayName}
75
- />
76
- ),
77
- };
78
- }),
79
67
  // A project script that BUILDS an Object3D opens as the live modeling
80
68
  // document; anything else keeps the text preview it is handed as `fallback`.
81
69
  module: lazy(async () => {
@@ -1,4 +1,4 @@
1
- import { announceDocumentStage } from '@volter/sdk/kit/document-viewports';
1
+ import { announceDocumentStage } from '@volter/sdk/document-viewports';
2
2
  import { lazy, Suspense, useEffect } from 'react';
3
3
  import type { ToolObject3DAuthoringProps, ToolObject3DPreviewProps } from '../../object3d-contributions';
4
4
 
@@ -3,18 +3,12 @@
3
3
  * Frame, pivot, snap and the vertex-snap hold, bound for the stage whose
4
4
  * document is ACTIVE.
5
5
  *
6
- * They belong to the STAGE, and until 2026-09-19 they were mounted from
7
- * `stage-overlay-set.tsx`, which a stage only reaches when it has the shell
8
- * above it (`StageHost.tsx`'s `shellStore && !chromeless`). A document
9
- * contributed by a PACKAGE mounts outside `EditorProvider`, so the Model
10
- * document reached NONE of them: measured live in the Code-OSS frame on a
11
- * models project, the editor's action table held 29 entries — the shell set
12
- * alone — and the stage's nine (`transform.combined/translate/rotate/scale`,
13
- * `viewport.toggleSnap/frameSelection/cyclePivot/snapToFloor`, the four view
14
- * presets) were absent, so G/R/S on the Model stage answered nothing at all.
15
- * The overlay set is an OVERLAY condition (a bounded host must not pay for
16
- * the collaboration client it reaches); which keys a stage answers is not,
17
- * and the two were one line by accident.
6
+ * They belong to the STAGE, not to `stage-overlay-set.tsx`, which a stage
7
+ * only reaches when it has the shell above it (`StageHost.tsx`'s
8
+ * `shellStore && !chromeless`): a document contributed by a PACKAGE mounts
9
+ * outside `EditorProvider` and still answers G/R/S. The overlay set is an
10
+ * OVERLAY condition (a bounded host must not pay for the collaboration client
11
+ * it reaches); which keys a stage answers is not.
18
12
  *
19
13
  * The ONE registry still means ONE holder (`hotkeys.ts`'s scope table, and
20
14
  * `key-actions.ts`'s action table under the frame): the active
@@ -2,26 +2,26 @@
2
2
  * hints and statistics. Package documents supply their own chrome. */
3
3
 
4
4
  import { useSyncExternalStore } from 'react';
5
- import { activeAuthoringVersion, subscribeActiveAuthoring } from '@volter/sdk/kit/authoring/active-adapter';
5
+ import { activeAuthoringVersion, subscribeActiveAuthoring } from '@volter/sdk/authoring/active-adapter';
6
6
  import {
7
7
  object3DDocumentSessionsVersion,
8
8
  subscribeObject3DDocumentSessions,
9
9
  } from '../authoring/object3d-document-session-registry';
10
10
  import type { EditorShellStore } from '../editor-shell-store';
11
11
  import type { EditorViewport } from '../editor-viewport';
12
- import { documentStageContext, type StageChrome, threeSelectionToolsApply } from '@volter/sdk/kit/stage-context';
12
+ import { documentStageContext, type StageChrome, stageSelectionToolsApply } from '@volter/sdk/stage-context';
13
13
  import {
14
14
  subscribeWorkspaceDocuments,
15
15
  workspaceDocumentRegistryVersion,
16
- } from '@volter/sdk/kit/workspace-document-registry';
16
+ } from '@volter/sdk/workspace-document-registry';
17
17
  import {
18
18
  subscribeViewportPresentation,
19
19
  viewPresentation,
20
20
  viewportPresentationVersion,
21
- } from '@volter/sdk/kit/viewport-presentation';
21
+ } from '@volter/sdk/viewport-presentation';
22
22
  import { CameraInfo } from './CameraInfo';
23
23
  import { StatsOverlay } from './StatsOverlay';
24
- import { TransientHintOverlay } from '@volter/sdk/kit/components/TransientHint';
24
+ import { TransientHintOverlay } from '@volter/sdk/components/TransientHint';
25
25
  import { ViewportOverlay } from './ViewportOverlay';
26
26
 
27
27
  /** What the HOST hands over about the stage itself — its own store, the
@@ -52,8 +52,7 @@ export function StageOverlaySet({
52
52
  active,
53
53
  chrome,
54
54
  }: StageOverlaySetProps) {
55
- /** THIS stage's own store — the shell's until the stage has finished
56
- * building, which is the same store every overlay read before unit 4. */
55
+ /** Use the stage's store once built, and the shell store during bootstrap. */
57
56
  const stageStore = stage?.store ?? store;
58
57
  useSyncExternalStore(store.shell.subscribe, store.shell.getShellSnapshot ?? store.shell.getSnapshot);
59
58
  // The stage's own state (its grid, helpers, stats, tool mode) lives on ITS
@@ -64,42 +63,34 @@ export function StageOverlaySet({
64
63
  useSyncExternalStore(subscribeWorkspaceDocuments, workspaceDocumentRegistryVersion);
65
64
  // THIS stage's context — not the focused one. A capability is present where
66
65
  // its condition holds, and the condition is asked of the stage that would
67
- // draw it (ARCHITECTURE-CORE §One stage).
66
+ // draw it.
68
67
  const ctx = documentStageContext(store.shell, documentId, chrome);
69
68
  /** Whether the selection tools draw here — and that is the WHOLE condition:
70
69
  * they read THIS stage's own store (`stage.store`, the one the shared
71
70
  * panels reach through `focusedStageStore()`), so a prefab's grid button
72
71
  * toggles the prefab's grid and its readout reports the camera its reader
73
72
  * is looking through. */
74
- const showsSelectionTools = threeSelectionToolsApply(ctx);
73
+ const showsSelectionTools = stageSelectionToolsApply(ctx);
75
74
  // The camera readout is the editor's own; a view whose target draws none leaves it off
76
75
  // (`overlays.cameraReadout`).
77
76
  useSyncExternalStore(subscribeViewportPresentation, viewportPresentationVersion, viewportPresentationVersion);
78
77
  const cameraReadout = viewPresentation(documentId).overlays.cameraReadout;
79
78
  return (
80
79
  <>
81
- {/* THE STAGE'S OWN KEYBOARD is no longer mounted here: this module is
82
- reached only by a stage that has the shell above it, and a
83
- package-contributed document (the Model document) has none — so it
84
- answered no stage key at all. It is `StageHost.tsx`'s now, beside
85
- the stage handle, under the same activation condition
86
- (`stage-keyboard.tsx` carries the measurement). */}
80
+ {/* StageHost mounts the stage keyboard beside its handle under the stage's activation condition. */}
87
81
  {showsSelectionTools ? (
88
82
  <>
89
83
  <ViewportOverlay store={stageStore} documentId={documentId} />
90
84
  {cameraReadout ? <CameraInfo /> : null}
91
85
  </>
92
86
  ) : null}
93
- {ctx.surface === 'three' && stageStore.showStats && <StatsOverlay />}
87
+ {ctx.surface === 'stage' && stageStore.showStats && <StatsOverlay />}
94
88
  {/* H2 — the ONE transient hint channel. Lives here because a refused
95
89
  transform is a viewport gesture; the overlay is absent from the DOM
96
90
  entirely until something has a reason to say. ONE channel means one
97
91
  mount — the ACTIVE stage's — so a hint never draws twice. */}
98
92
  {active ? <TransientHintOverlay /> : null}
99
- {/* W4 (inventory row V9): the scene document mounts NO network overlay.
100
- Compact connection state lives in the status bar
101
- (`status-contributions.tsx`); the detailed surface is the `network`
102
- workspace utility (`NetworkInspectorPanel`), revealed from there. */}
93
+ {/* Connection state belongs to the status bar; the network utility provides detail. */}
103
94
  </>
104
95
  );
105
96
  }
@@ -1,36 +1,15 @@
1
- /**
2
- * THE OTHER PARTICIPANTS, ON A STAGE — the spatial half of collaboration
3
- * presence: what THIS stage publishes about the reader's own camera and
4
- * gestures, and the camera frusta, selection boxes and pointer rays it paints
5
- * for everyone else (WORK.md §Presence and the substrate, presence unit 4).
6
- *
7
- * It was the scene panel's, so only the world root had it. Under
8
- * ARCHITECTURE-CORE §One stage it is a CAPABILITY with a condition — a stage
9
- * painting a three surface — so every 3D document mounts it and a prefab
10
- * story shows the same markers the Scene does.
11
- *
12
- * WHICH STAGE A PARTICIPANT IS ON. Presence is ONE record per participant
13
- * (`collaboration-presence.ts`), and its `document` field is written by the
14
- * shell for the ACTIVE workspace document (`DefaultEditorLayout.tsx`). So a
15
- * stage publishes its camera only while its own document is the active one —
16
- * otherwise several stages would overwrite each other's pose in the single
17
- * record — and paints a participant only on the stage whose id matches that
18
- * participant's `presence.document`. Measured against the spec's "publish it
19
- * from the host with the stage's documentId": the presence record has ONE
20
- * `document` field and the shell already owns it, so the stage owns the
21
- * CONDITION on writing, not a second writer of that field.
22
- */
1
+ /** Publish this stage’s camera only while its document is active, and draw each participant on the stage named by presence.document. The shell owns the document field. */
23
2
 
24
- import type { CollaborationParticipant } from '@volter/sdk/session/collaboration-types';
3
+ import { editorHost } from '@volter/sdk/host';
4
+ import type { CollaborationParticipant } from '@volter/sdk/collaboration-types';
25
5
  import { EDITOR_THEME_CLASS, graphiteDarkEditorTheme } from '@volter/sdk/widgets';
26
6
  import { contentWorldBounds } from '@volter/editor-threejs/viewport/content-bounds';
27
7
  import * as THREE from 'three';
28
- import { collaborationSnapshot } from '@volter/sdk/kit/collaboration-client';
29
- import { reportCollaborationPresence } from '@volter/sdk/kit/collaboration-presence';
30
- import { EDITOR_PARTICIPANT_ID } from '@volter/sdk/kit/editor-presence';
8
+ import { reportCollaborationPresence } from '../collaboration-presence';
9
+ import { COLLABORATION_PARTICIPANT_ID as EDITOR_PARTICIPANT_ID } from '@volter/sdk/editor-session-attribution';
31
10
  import type { EditorShellStore } from '../editor-shell-store';
32
11
  import type { EditorViewport } from '../editor-viewport';
33
- import { activeWorkspaceDocumentId } from '@volter/sdk/kit/workspace-document-registry';
12
+ import { activeWorkspaceDocumentId } from '@volter/sdk/workspace-document-registry';
34
13
 
35
14
  interface RemoteCameraMarker {
36
15
  camera: THREE.PerspectiveCamera;
@@ -58,9 +37,7 @@ interface RemoteRayMarker {
58
37
  /**
59
38
  * Remote-camera labels paint into a DETACHED canvas, and a 2D context cannot
60
39
  * resolve `var(--volter-…)` — so the theme's value has to be read off the live
61
- * theme root before it reaches `fillStyle`. Before a theme is installed (unit
62
- * environments), fall back to the default theme's own value rather than a raw
63
- * literal, so the label never depends on a color the theme contract doesn't own.
40
+ * theme root before it reaches `fillStyle`. Use the default theme's value during bootstrap.
64
41
  */
65
42
  function contentOnAccentColor(): string {
66
43
  const root = document.querySelector(`.${EDITOR_THEME_CLASS}`);
@@ -324,7 +301,7 @@ export function bindStagePresenceMarkers(options: StagePresenceOptions): StagePr
324
301
  const remoteRayMarkers = new Map<string, RemoteRayMarker>();
325
302
  let remotePresenceSequence = -1;
326
303
  const syncRemoteSpatialMarkers = (): void => {
327
- const snapshot = collaborationSnapshot();
304
+ const snapshot = editorHost().collaboration.snapshot();
328
305
  if (!snapshot || snapshot.sequence === remotePresenceSequence) return;
329
306
  remotePresenceSequence = snapshot.sequence;
330
307
  /** THE PAINT CONDITION: a participant is on the stage its own presence
@@ -409,7 +386,7 @@ export function bindStagePresenceMarkers(options: StagePresenceOptions): StagePr
409
386
  if (!publishes()) return;
410
387
  const participantId = (event as CustomEvent<{ participantId?: unknown }>).detail?.participantId;
411
388
  if (typeof participantId !== 'string') return;
412
- const camera = collaborationSnapshot()?.participants.find(
389
+ const camera = editorHost().collaboration.snapshot()?.participants.find(
413
390
  (participant) => participant.participantId === participantId,
414
391
  )?.presence.camera;
415
392
  if (!camera) return;
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * What a three stage adds to its document's viewport
3
- * (`@volter/sdk/kit/document-viewports`): the transform-tool driver, read
3
+ * (`@volter/sdk/document-viewports`): the transform-tool driver, read
4
4
  * synchronously, and the shelf strip and header wells behind `lazy()`, so a
5
5
  * bounded host that mounts a stage never loads the editor's toolbar.
6
6
  */
7
7
  import { lazy } from 'react';
8
- import { documentStageContext, type StageTransformDriver, stageTransformDriver } from '@volter/sdk/kit/stage-context';
8
+ import { documentStageContext, type StageTransformDriver, stageTransformDriver } from '@volter/sdk/stage-context';
9
9
  import { threeStoreForHost } from '../three-state';
10
10
 
11
11
  /** What the host's transform tools drive on this document's stage. */
@@ -17,7 +17,7 @@ export function threeStageTransformDriver(documentId: string): StageTransformDri
17
17
  const TransformTools = lazy(() =>
18
18
  import('./stage-transform-tools').then((m) => ({ default: m.ThreeStageTransformTools })),
19
19
  );
20
- const TransformControls = lazy(() =>
20
+ const TransformSettings = lazy(() =>
21
21
  import('./stage-transform-tools').then((m) => ({ default: m.ThreeStageTransformControls })),
22
22
  );
23
23
 
@@ -25,6 +25,6 @@ export function threeStageTransformChrome(documentId: string) {
25
25
  return {
26
26
  transformTools: () => threeStageTransformDriver(documentId),
27
27
  TransformTools,
28
- TransformControls,
28
+ TransformSettings,
29
29
  } as const;
30
30
  }
@@ -1,27 +1,21 @@
1
1
  /**
2
2
  * A THREE STAGE'S TRANSFORM TOOLS, as the stage contributes them to its
3
- * document's chrome (`@volter/sdk/kit/document-viewports`): what the
3
+ * document's chrome (`@volter/sdk/document-viewports`): what the
4
4
  * host's transform tools drive on it (`stage-context.ts`'s one answer), the tool
5
5
  * strip on its shelf and the gizmo's header wells. Every tool writes THIS
6
6
  * stage's own store (`stage-store-registry.ts`); the world root's stage runs on
7
7
  * the session store, so the Scene document is unchanged either way.
8
8
  */
9
- import { stageTransformDoor } from '@volter/sdk/contributions';
10
- import { type ViewportInteraction, viewPresentation } from '@volter/sdk/kit/viewport-presentation';
9
+ import { type ViewportInteraction, viewPresentation } from '@volter/sdk/viewport-presentation';
11
10
  import { useEffect, useSyncExternalStore } from 'react';
12
- import { useEditorStore } from '@volter/sdk/kit/editor-runtime';
11
+ import { useEditorStore } from '@volter/sdk/editor-runtime';
13
12
  import type { EditorShellStore } from '../editor-shell-store';
14
13
  import { threeStageTransformDriver } from './stage-transform-chrome';
15
- import { stageStore, stageStoresVersion, subscribeStageStores } from '@volter/sdk/kit/stage-store-registry';
14
+ import { stageStore, stageStoresVersion, subscribeStageStores } from '@volter/sdk/stage-store-registry';
16
15
  import { threeStateOf } from '../three-state';
17
- import { ToolStrip, TransformHeaderControls } from '@volter/sdk/kit/components/Toolbar';
16
+ import { ToolStrip, TransformHeaderControls } from '@volter/sdk/components/Toolbar';
18
17
 
19
- /**
20
- * THE VIEWS WHOSE SHELF HAS ALREADY OPENED, per store — keyed by the VIEW as well, because
21
- * several views can share one store (measured: a restored `main` armed the shared store, and
22
- * HeroBox opened after it never got its own boot tool). A `WeakMap` because a store outlives
23
- * nothing here.
24
- */
18
+ /** Track boot-tool activation per store and view, since multiple views can share one store. */
25
19
  const shelfOpened = new WeakMap<EditorShellStore, Set<string>>();
26
20
 
27
21
  /**
@@ -62,8 +56,7 @@ export function ThreeStageTransformTools({ documentId }: { readonly documentId:
62
56
  if (driver === 'gizmo') armShelfBootTool(own && threeStateOf(own), documentId);
63
57
  }, [driver, own, documentId]);
64
58
  if (driver === 'none') return null;
65
- const door = driver === 'modal' ? stageTransformDoor(documentId) : null;
66
- return <ToolStrip store={own ?? shell} {...(door ? { door } : {})} />;
59
+ return <ToolStrip store={own ?? shell} />;
67
60
  }
68
61
 
69
62
  export function ThreeStageTransformControls({ documentId }: { readonly documentId: string }) {