@fps-games/editor 0.2.0-beta.3 → 0.2.0-beta.4

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 (116) hide show
  1. package/dist/agent-bridge/capabilities/editor-state.d.ts.map +1 -1
  2. package/dist/agent-bridge/capabilities/editor-state.js +26 -17
  3. package/dist/agent-bridge/capabilities/editor-state.js.map +1 -1
  4. package/dist/agent-bridge/capabilities/prefabs.d.ts.map +1 -1
  5. package/dist/agent-bridge/capabilities/prefabs.js +61 -13
  6. package/dist/agent-bridge/capabilities/prefabs.js.map +1 -1
  7. package/dist/agent-bridge/capabilities/scene-objects.d.ts +1 -1
  8. package/dist/agent-bridge/capabilities/scene-objects.d.ts.map +1 -1
  9. package/dist/agent-bridge/capabilities/scene-objects.js +110 -25
  10. package/dist/agent-bridge/capabilities/scene-objects.js.map +1 -1
  11. package/dist/agent-bridge/capabilities/selection.d.ts.map +1 -1
  12. package/dist/agent-bridge/capabilities/selection.js +8 -5
  13. package/dist/agent-bridge/capabilities/selection.js.map +1 -1
  14. package/dist/agent-bridge/capabilities/shadows.d.ts.map +1 -1
  15. package/dist/agent-bridge/capabilities/shadows.js +11 -2
  16. package/dist/agent-bridge/capabilities/shadows.js.map +1 -1
  17. package/dist/agent-bridge/capabilities/transform.d.ts.map +1 -1
  18. package/dist/agent-bridge/capabilities/transform.js +6 -4
  19. package/dist/agent-bridge/capabilities/transform.js.map +1 -1
  20. package/dist/agent-bridge/capabilities/viewport.d.ts.map +1 -1
  21. package/dist/agent-bridge/capabilities/viewport.js +79 -47
  22. package/dist/agent-bridge/capabilities/viewport.js.map +1 -1
  23. package/dist/agent-bridge/shared.d.ts +8 -3
  24. package/dist/agent-bridge/shared.d.ts.map +1 -1
  25. package/dist/agent-bridge/shared.js +32 -0
  26. package/dist/agent-bridge/shared.js.map +1 -1
  27. package/dist/build-info.json +34 -34
  28. package/dist/local-editor-agent-actions.d.ts.map +1 -1
  29. package/dist/local-editor-agent-actions.js +9 -5
  30. package/dist/local-editor-agent-actions.js.map +1 -1
  31. package/dist/local-editor-harness.d.ts.map +1 -1
  32. package/dist/local-editor-harness.js +264 -41
  33. package/dist/local-editor-harness.js.map +1 -1
  34. package/node_modules/@fps-games/babylon-renderer/package.json +7 -7
  35. package/node_modules/@fps-games/editor-babylon/package.json +5 -5
  36. package/node_modules/@fps-games/editor-browser/dist/index.d.ts +3 -1
  37. package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
  38. package/node_modules/@fps-games/editor-browser/dist/index.js +2 -0
  39. package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
  40. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +2 -1
  41. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
  42. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +260 -94
  43. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
  44. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-projection.d.ts +7 -0
  45. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-projection.d.ts.map +1 -0
  46. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-projection.js +186 -0
  47. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-projection.js.map +1 -0
  48. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-scope.d.ts +2 -2
  49. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-scope.d.ts.map +1 -1
  50. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-scope.js +14 -8
  51. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-scope.js.map +1 -1
  52. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts.map +1 -1
  53. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js +23 -7
  54. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js.map +1 -1
  55. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts +1 -1
  56. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -1
  57. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +6 -0
  58. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -1
  59. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts +3 -0
  60. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts.map +1 -1
  61. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js +60 -13
  62. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js.map +1 -1
  63. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-status-diagnostics.d.ts +24 -0
  64. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-status-diagnostics.d.ts.map +1 -0
  65. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-status-diagnostics.js +69 -0
  66. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-status-diagnostics.js.map +1 -0
  67. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +58 -14
  68. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
  69. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.js.map +1 -1
  70. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +1 -1
  71. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
  72. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +196 -11
  73. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
  74. package/node_modules/@fps-games/editor-browser/package.json +4 -4
  75. package/node_modules/@fps-games/editor-core/dist/editor-session.d.ts +3 -0
  76. package/node_modules/@fps-games/editor-core/dist/editor-session.d.ts.map +1 -1
  77. package/node_modules/@fps-games/editor-core/dist/editor-session.js +9 -0
  78. package/node_modules/@fps-games/editor-core/dist/editor-session.js.map +1 -1
  79. package/node_modules/@fps-games/editor-core/package.json +6 -6
  80. package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
  81. package/node_modules/@fps-games/editor-playable-sdk/package.json +6 -6
  82. package/node_modules/@fps-games/editor-protocol/package.json +2 -2
  83. package/node_modules/@fps-games/editor-tooling/package.json +3 -3
  84. package/node_modules/@fps-games/playable-assets/package.json +1 -1
  85. package/node_modules/@fps-games/playable-contracts/package.json +1 -1
  86. package/node_modules/@fps-games/playable-rendering/package.json +2 -2
  87. package/node_modules/@fps-games/playable-runtime/package.json +12 -12
  88. package/node_modules/@fps-games/playable-runtime-data/package.json +4 -4
  89. package/node_modules/@fps-games/playable-scene/package.json +2 -2
  90. package/node_modules/@fps-games/plugin-api/package.json +1 -1
  91. package/node_modules/@fps-games/plugin-assets/dist/version.d.ts +1 -1
  92. package/node_modules/@fps-games/plugin-assets/dist/version.js +1 -1
  93. package/node_modules/@fps-games/plugin-assets/dist/version.js.map +1 -1
  94. package/node_modules/@fps-games/plugin-assets/package.json +5 -5
  95. package/node_modules/@fps-games/plugin-host/package.json +2 -2
  96. package/node_modules/@fps-games/plugin-markers/dist/version.d.ts +1 -1
  97. package/node_modules/@fps-games/plugin-markers/dist/version.js +1 -1
  98. package/node_modules/@fps-games/plugin-markers/dist/version.js.map +1 -1
  99. package/node_modules/@fps-games/plugin-markers/package.json +5 -5
  100. package/node_modules/@fps-games/plugin-materials/dist/version.d.ts +1 -1
  101. package/node_modules/@fps-games/plugin-materials/dist/version.js +1 -1
  102. package/node_modules/@fps-games/plugin-materials/dist/version.js.map +1 -1
  103. package/node_modules/@fps-games/plugin-materials/package.json +5 -5
  104. package/node_modules/@fps-games/plugin-rendering/dist/version.d.ts +1 -1
  105. package/node_modules/@fps-games/plugin-rendering/dist/version.js +1 -1
  106. package/node_modules/@fps-games/plugin-rendering/dist/version.js.map +1 -1
  107. package/node_modules/@fps-games/plugin-rendering/package.json +5 -5
  108. package/node_modules/@fps-games/plugin-scene/dist/version.d.ts +1 -1
  109. package/node_modules/@fps-games/plugin-scene/dist/version.js +1 -1
  110. package/node_modules/@fps-games/plugin-scene/dist/version.js.map +1 -1
  111. package/node_modules/@fps-games/plugin-scene/package.json +5 -5
  112. package/node_modules/@fps-games/plugin-shadows/dist/version.d.ts +1 -1
  113. package/node_modules/@fps-games/plugin-shadows/dist/version.js +1 -1
  114. package/node_modules/@fps-games/plugin-shadows/dist/version.js.map +1 -1
  115. package/node_modules/@fps-games/plugin-shadows/package.json +7 -7
  116. package/package.json +19 -19
@@ -1,6 +1,6 @@
1
1
  import { createEditorSession, FPS_EDITOR_ACTIVE_TOOL_CHANGED, FPS_EDITOR_DOCUMENT_CHANGED, FPS_EDITOR_HISTORY_CHANGED, FPS_EDITOR_SELECTION_CHANGED, createEditorAgentBridge, createInspectorRegistry, DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_VIEWPORT_TOOL_STATE, createEmptyEditorViewportSpatialOverlayState, cloneEditorTransformSnapshot as cloneCoreEditorTransformSnapshot, composeEditorTransformMatrix, editorTransformMatricesAlmostEqual, cloneEditorViewportToolState, cloneEditorViewportSpatialOverlayState, aggregateInspectorObjectsToMultiObject, createInspectorEditPayload, compareInspectorSections, computeEditorTransformActionTargets, mergeInspectorSections, isEditorTransformTrsSnapshot, normalizeEditorTransformConstraint, readEditorTransformPosition, resolveEditorSelectionCommand, validateSceneGraphDelete, validateSceneGraphDrop, validateSceneGraphGroupSelection, validateSceneGraphMove, validateSceneGraphRename, } from '@fps-games/editor-core';
2
2
  import {} from '@fps-games/editor-protocol';
3
- import { createLocalEditorBrowserUi, toTransformOperationPolicyStatusLabel, } from '@fps-games/editor-browser';
3
+ import { createLocalEditorBrowserUi, createLocalEditorActiveHierarchyScope, createLocalEditorHierarchyProjection, isLocalEditorHierarchyCommandPreconditionCurrent, resolveLocalEditorStatusDiagnostics, createLocalEditorSceneHierarchyScope, createLocalEditorPrefabStageHierarchyScope, toTransformOperationPolicyStatusLabel, } from '@fps-games/editor-browser';
4
4
  import { createLocalEditorHarnessPluginInspectorOptions } from './local-editor-plugin-inspector.js';
5
5
  import { registerLocalEditorHarnessLifecycleOperations, } from './internal/local-editor-lifecycle-operations.js';
6
6
  import { createLocalEditorPluginPanelState } from './local-editor-plugin-panel.js';
@@ -259,6 +259,9 @@ function createLocalEditorHarnessImplementation(options, construction) {
259
259
  scopeOperations: null,
260
260
  nextPrefabScopeRevision: 0,
261
261
  editorStateRevision: 0,
262
+ hierarchyProjectionRevision: 0,
263
+ hierarchyCommandDiagnostics: [],
264
+ hierarchyReconcileDiagnostics: [],
262
265
  sceneArtifactRevision: 0,
263
266
  busy: false,
264
267
  session: null,
@@ -278,6 +281,8 @@ function createLocalEditorHarnessImplementation(options, construction) {
278
281
  overrides: {},
279
282
  isolatedObjectIds: [],
280
283
  },
284
+ previewVisibilityScopeId: 'scene',
285
+ previewVisibilityByScope: new Map(),
281
286
  markerOverlay: null,
282
287
  markerGraphVisibility: createDefaultMarkerGraphVisibilityState(),
283
288
  activeMarkerRelationId: null,
@@ -332,7 +337,10 @@ function createLocalEditorHarnessImplementation(options, construction) {
332
337
  agentBridgeTransport: null,
333
338
  agentBridgeStatus: createDisabledLocalEditorAgentBridgeTransportStatus(),
334
339
  };
340
+ state.previewVisibilityByScope.set(state.previewVisibilityScopeId, state.previewVisibility);
335
341
  const handleEditorSessionChange = (change) => {
342
+ if (change.documentChanged || change.selectionChanged)
343
+ advanceHierarchyProjectionRevision(state);
336
344
  void lifecycleController?.synchronizeDocumentState(change.dirty).catch(error => {
337
345
  console.error('[LocalEditorHarness] lifecycle document synchronization failed', error);
338
346
  });
@@ -389,6 +397,13 @@ function createLocalEditorHarnessImplementation(options, construction) {
389
397
  };
390
398
  let harness;
391
399
  let agentLifecycleController;
400
+ const acceptBrowserHierarchyCommandPrecondition = (precondition) => {
401
+ const hadCommandDiagnostics = state.hierarchyCommandDiagnostics.length > 0;
402
+ const accepted = acceptHierarchyCommandPrecondition(state, precondition);
403
+ if (accepted && hadCommandDiagnostics)
404
+ harness.render();
405
+ return accepted;
406
+ };
392
407
  const pluginInspector = createLocalEditorHarnessPluginInspectorOptions(options.pluginHost, options.inspector);
393
408
  const ui = createLocalEditorBrowserUi({
394
409
  root,
@@ -429,6 +444,12 @@ function createLocalEditorHarnessImplementation(options, construction) {
429
444
  harness.render();
430
445
  },
431
446
  onAssetAction: (input) => {
447
+ const hierarchyAction = input.actionId.startsWith('prefab.node.')
448
+ || input.hierarchyPrecondition !== undefined;
449
+ if (hierarchyAction && !acceptBrowserHierarchyCommandPrecondition(input.hierarchyPrecondition)) {
450
+ harness.render();
451
+ return;
452
+ }
432
453
  void runExclusive(state, harness.render, async () => {
433
454
  if (await handleBrowserAssetAction(state, options, input))
434
455
  harness.render();
@@ -440,14 +461,26 @@ function createLocalEditorHarnessImplementation(options, construction) {
440
461
  harness.render();
441
462
  });
442
463
  },
443
- onSelectPrefabStageItem: (itemId) => {
464
+ onSelectPrefabStageItem: (itemId, precondition) => {
465
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
466
+ harness.render();
467
+ return;
468
+ }
444
469
  if (selectPrefabStageItemWithPreviewSync(state, options, itemId))
445
470
  harness.render();
446
471
  },
447
- onSelectHierarchyItem: (input) => {
472
+ onSelectHierarchyItem: (input, precondition) => {
473
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
474
+ harness.render();
475
+ return;
476
+ }
448
477
  selectItem(state, options, input, harness.render);
449
478
  },
450
- onHierarchyItemEnabledChange: (input) => {
479
+ onHierarchyItemEnabledChange: (input, precondition) => {
480
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
481
+ harness.render();
482
+ return;
483
+ }
451
484
  if (patchHierarchyItemEnabled(state, options, input))
452
485
  harness.render();
453
486
  },
@@ -459,35 +492,63 @@ function createLocalEditorHarnessImplementation(options, construction) {
459
492
  if (patchMarkerRelation(state, options, input))
460
493
  harness.render();
461
494
  },
462
- onSelectionCommand: (command) => {
495
+ onSelectionCommand: (command, precondition) => {
496
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
497
+ harness.render();
498
+ return;
499
+ }
463
500
  dispatchSelectionCommand(state, options, command, harness.render);
464
501
  },
465
- onSceneGraphRename: (intent) => {
502
+ onSceneGraphRename: (intent, precondition) => {
503
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
504
+ harness.render();
505
+ return;
506
+ }
466
507
  if (renameSceneGraphNode(state, options, intent))
467
508
  harness.render();
468
509
  },
469
- onSceneGraphCreateGroup: (intent) => {
510
+ onSceneGraphCreateGroup: (intent, precondition) => {
511
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
512
+ harness.render();
513
+ return;
514
+ }
470
515
  if (createSceneGraphGroup(state, options, intent))
471
516
  harness.render();
472
517
  },
473
518
  onSceneGraphCreatePrimitive: options.documentAdapter.createSceneGraphCreatePrimitivePatch
474
- ? (intent) => {
519
+ ? (intent, precondition) => {
520
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
521
+ harness.render();
522
+ return;
523
+ }
475
524
  if (createSceneGraphPrimitive(state, options, intent))
476
525
  harness.render();
477
526
  }
478
527
  : undefined,
479
528
  onSceneGraphCreateMarker: options.documentAdapter.createMarkerGraphPatch
480
- ? (intent) => {
529
+ ? (intent, precondition) => {
530
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
531
+ harness.render();
532
+ return;
533
+ }
481
534
  if (createSceneGraphMarker(state, options, intent))
482
535
  harness.render();
483
536
  }
484
537
  : undefined,
485
- onSceneGraphDelete: (intent) => {
538
+ onSceneGraphDelete: (intent, precondition) => {
539
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
540
+ harness.render();
541
+ return;
542
+ }
486
543
  if (deleteSceneGraphNodes(state, options, intent))
487
544
  harness.render();
488
545
  },
489
546
  onSceneGraphDuplicate: options.documentAdapter.createDuplicateSelectionPatch
490
- ? (intent) => {
547
+ ? (intent, precondition) => {
548
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
549
+ harness.render();
550
+ return;
551
+ }
491
552
  if (duplicateSceneGraphNodes(state, options, {
492
553
  targetIds: intent.targetIds,
493
554
  activeId: intent.activeId ?? null,
@@ -495,23 +556,41 @@ function createLocalEditorHarnessImplementation(options, construction) {
495
556
  harness.render();
496
557
  }
497
558
  : undefined,
498
- onSceneGraphDrop: (intent) => {
559
+ onSceneGraphDrop: (intent, precondition) => {
560
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
561
+ harness.render();
562
+ return;
563
+ }
499
564
  if (dropSceneGraphNode(state, options, intent))
500
565
  harness.render();
501
566
  },
502
- onSceneGraphMove: (intent) => {
567
+ onSceneGraphMove: (intent, precondition) => {
568
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
569
+ harness.render();
570
+ return { ok: false, reason: 'Hierarchy projection is stale.' };
571
+ }
503
572
  const result = moveSceneGraphNodes(state, options, intent);
504
573
  if (result)
505
574
  harness.render();
506
575
  return result ?? { ok: false, reason: 'Editor is not ready.' };
507
576
  },
508
- onSceneGraphGroupSelection: (intent) => {
577
+ onSceneGraphGroupSelection: (intent, precondition) => {
578
+ if (!acceptBrowserHierarchyCommandPrecondition(precondition)) {
579
+ harness.render();
580
+ return;
581
+ }
509
582
  if (groupSceneGraphSelection(state, options, intent))
510
583
  harness.render();
511
584
  },
512
- onContextAction: (action) => {
585
+ onContextAction: (action, precondition) => {
586
+ if (action.region === 'hierarchy' && !acceptBrowserHierarchyCommandPrecondition(precondition)) {
587
+ harness.render();
588
+ return;
589
+ }
513
590
  if (action.region === 'hierarchy' && action.action === 'custom') {
514
- const result = runHierarchyCustomContextAction(state, options, action);
591
+ if (!precondition)
592
+ return;
593
+ const result = runHierarchyCustomContextAction(state, options, action, precondition);
515
594
  if (isLocalEditorPromiseLike(result)) {
516
595
  void result.then(changed => {
517
596
  if (changed)
@@ -756,6 +835,7 @@ function createLocalEditorHarnessImplementation(options, construction) {
756
835
  onChange: handleEditorSessionChange,
757
836
  onChangeError: handleEditorSessionObserverError,
758
837
  });
838
+ advanceHierarchyProjectionRevision(state);
759
839
  try {
760
840
  attachLifecycleOwnedSessionAuthority();
761
841
  }
@@ -854,6 +934,7 @@ function createLocalEditorHarnessImplementation(options, construction) {
854
934
  };
855
935
  harness = {
856
936
  render() {
937
+ reconcileHierarchySelectionBeforeRender(state, options);
857
938
  syncViewportCameraState(state);
858
939
  syncViewportMeasurementState(state);
859
940
  syncViewportSpatialOverlay(state, options);
@@ -1064,6 +1145,7 @@ function createLocalEditorHarnessImplementation(options, construction) {
1064
1145
  onChange: handleEditorSessionChange,
1065
1146
  onChangeError: handleEditorSessionObserverError,
1066
1147
  });
1148
+ advanceHierarchyProjectionRevision(state);
1067
1149
  try {
1068
1150
  attachLifecycleOwnedSessionAuthority();
1069
1151
  }
@@ -1157,10 +1239,10 @@ function createLocalEditorHarnessImplementation(options, construction) {
1157
1239
  }),
1158
1240
  selectPrefabStageItem: itemId => selectPrefabStageItemWithPreviewSync(state, options, itemId),
1159
1241
  runPrefabStageAssetAction: input => patchBrowserAssetAction(state, options, input),
1160
- getHierarchy: () => createUiState(state, options).hierarchy,
1242
+ getHierarchyProjection: () => createUiState(state, options).hierarchyProjection ?? null,
1243
+ getStatusDiagnostics: () => createUiState(state, options).statusDiagnostics ?? [],
1161
1244
  dispatchSelectionCommand: (command, shouldRenderUi = false) => dispatchSelectionCommand(state, options, command, shouldRenderUi ? () => harness.render() : undefined),
1162
1245
  focusProjectionNode: objectId => frameViewportProjectionNode(state, objectId),
1163
- focusSelectedProjection: () => frameSelectedViewportProjection(state, options),
1164
1246
  syncViewportCameraState: () => syncViewportCameraState(state),
1165
1247
  requestSceneFrame: reason => requestEditorSceneFrame(state, reason),
1166
1248
  cloneTransformSnapshot: transform => cloneEditorTransformSnapshot(transform),
@@ -2238,7 +2320,9 @@ function clearPrefabStageForEditorTeardown(state) {
2238
2320
  const stage = state.prefabStage;
2239
2321
  if (!stage)
2240
2322
  return;
2323
+ switchPreviewVisibilityScope(state, 'scene');
2241
2324
  state.prefabStage = null;
2325
+ advanceHierarchyProjectionRevision(state);
2242
2326
  setGridVisibleForEditorContext(state, stage.restoreGridVisible, 'prefab-stage-editor-teardown-grid');
2243
2327
  state.sceneCameraPreviewEnabled = stage.restoreSceneCameraPreviewEnabled;
2244
2328
  if (!stage.restoreSceneCameraPreviewEnabled)
@@ -2533,7 +2617,7 @@ function createBrowserContextForCustomAction(state, options, action) {
2533
2617
  activeId: action.activeId,
2534
2618
  };
2535
2619
  }
2536
- function runHierarchyCustomContextAction(state, options, action) {
2620
+ function runHierarchyCustomContextAction(state, options, action, precondition) {
2537
2621
  const browserContext = createBrowserContextForCustomAction(state, options, action);
2538
2622
  const context = browserContext
2539
2623
  ? createHarnessHierarchyContextActionContext(state, options, browserContext, action.payload)
@@ -2544,14 +2628,21 @@ function runHierarchyCustomContextAction(state, options, action) {
2544
2628
  .find(candidate => candidate.id === action.id);
2545
2629
  if (!registration)
2546
2630
  return false;
2631
+ const applyIfCurrent = (value) => {
2632
+ const hadCommandDiagnostics = state.hierarchyCommandDiagnostics.length > 0;
2633
+ if (!acceptHierarchyCommandPrecondition(state, precondition))
2634
+ return true;
2635
+ return applyHierarchyContextActionResult(state, options, value, registration.label)
2636
+ || hadCommandDiagnostics;
2637
+ };
2547
2638
  try {
2548
2639
  const result = registration.run(context);
2549
2640
  if (isLocalEditorPromiseLike(result)) {
2550
2641
  return Promise.resolve(result)
2551
- .then(value => applyHierarchyContextActionResult(state, options, value, registration.label))
2642
+ .then(applyIfCurrent)
2552
2643
  .catch(error => reportHierarchyContextActionError(state, registration.label, error));
2553
2644
  }
2554
- return applyHierarchyContextActionResult(state, options, result, registration.label);
2645
+ return applyIfCurrent(result);
2555
2646
  }
2556
2647
  catch (error) {
2557
2648
  return reportHierarchyContextActionError(state, registration.label, error);
@@ -2713,6 +2804,64 @@ function dispatchSelectionCommand(state, options, command, renderUi) {
2713
2804
  }
2714
2805
  return true;
2715
2806
  }
2807
+ function acceptHierarchyCommandPrecondition(state, precondition) {
2808
+ const documentRevision = state.session?.getState({ cloneDocuments: false }).documentRevision ?? 0;
2809
+ const scopeId = state.prefabStage
2810
+ ? `prefab-stage:${state.prefabStage.descriptor.assetId}`
2811
+ : 'scene';
2812
+ const expected = {
2813
+ scopeId,
2814
+ documentRevision,
2815
+ projectionRevision: state.hierarchyProjectionRevision,
2816
+ };
2817
+ if (!precondition) {
2818
+ state.hierarchyCommandDiagnostics = [{
2819
+ code: 'hierarchy-command-precondition-missing',
2820
+ severity: 'error',
2821
+ source: 'hierarchy-command',
2822
+ message: `Hierarchy command rejected because no projection revision was provided (expected ${formatHierarchyRevision(expected)}).`,
2823
+ ...expected,
2824
+ details: {
2825
+ expectedScopeId: expected.scopeId,
2826
+ expectedDocumentRevision: expected.documentRevision,
2827
+ expectedProjectionRevision: expected.projectionRevision,
2828
+ },
2829
+ }];
2830
+ return false;
2831
+ }
2832
+ const accepted = isLocalEditorHierarchyCommandPreconditionCurrent(precondition, expected);
2833
+ if (accepted) {
2834
+ state.hierarchyCommandDiagnostics = [];
2835
+ return true;
2836
+ }
2837
+ const message = `Hierarchy command rejected because its projection is stale (expected ${formatHierarchyRevision(expected)}, received ${formatHierarchyRevision(precondition)}).`;
2838
+ state.hierarchyCommandDiagnostics = [{
2839
+ code: 'hierarchy-command-stale',
2840
+ severity: 'error',
2841
+ source: 'hierarchy-command',
2842
+ message,
2843
+ scopeId,
2844
+ documentRevision,
2845
+ projectionRevision: state.hierarchyProjectionRevision,
2846
+ details: {
2847
+ expectedScopeId: expected.scopeId,
2848
+ expectedDocumentRevision: expected.documentRevision,
2849
+ expectedProjectionRevision: expected.projectionRevision,
2850
+ receivedScopeId: precondition.scopeId,
2851
+ receivedDocumentRevision: precondition.documentRevision,
2852
+ receivedProjectionRevision: precondition.projectionRevision,
2853
+ },
2854
+ }];
2855
+ return false;
2856
+ }
2857
+ function formatHierarchyRevision(precondition) {
2858
+ return `${precondition.scopeId}@document:${precondition.documentRevision}/projection:${precondition.projectionRevision}`;
2859
+ }
2860
+ function advanceHierarchyProjectionRevision(state) {
2861
+ state.hierarchyProjectionRevision += 1;
2862
+ state.hierarchyCommandDiagnostics = [];
2863
+ state.hierarchyReconcileDiagnostics = [];
2864
+ }
2716
2865
  function consumeMarkerRelationPickSelectionCommand(state, options, command, renderUi) {
2717
2866
  const pick = state.markerRelationPick;
2718
2867
  if (!pick?.active)
@@ -4056,6 +4205,7 @@ async function enterPrefabStage(state, options, input) {
4056
4205
  return true;
4057
4206
  }
4058
4207
  }
4208
+ switchPreviewVisibilityScope(state, `prefab-stage:${descriptor.assetId}`);
4059
4209
  state.prefabStage = {
4060
4210
  descriptor,
4061
4211
  asset,
@@ -4065,6 +4215,7 @@ async function enterPrefabStage(state, options, input) {
4065
4215
  restoreGridVisible: previousPrefabStage?.restoreGridVisible ?? state.gridVisible,
4066
4216
  restoreSceneCameraPreviewEnabled: previousPrefabStage?.restoreSceneCameraPreviewEnabled ?? state.sceneCameraPreviewEnabled,
4067
4217
  };
4218
+ advanceHierarchyProjectionRevision(state);
4068
4219
  setGridVisibleForEditorContext(state, false, 'prefab-stage-enter-grid');
4069
4220
  state.sceneCameraPreviewEnabled = false;
4070
4221
  state.sceneCameraPreview?.setActive(false);
@@ -4087,7 +4238,9 @@ async function exitPrefabStage(state, options) {
4087
4238
  const restoreGridVisible = prefabStage.restoreGridVisible;
4088
4239
  const restoreSceneCameraPreviewEnabled = prefabStage.restoreSceneCameraPreviewEnabled;
4089
4240
  await closePrefabStageLifecycleScopes(state, prefabStage);
4241
+ switchPreviewVisibilityScope(state, 'scene');
4090
4242
  state.prefabStage = null;
4243
+ advanceHierarchyProjectionRevision(state);
4091
4244
  setGridVisibleForEditorContext(state, restoreGridVisible, 'prefab-stage-exit-grid');
4092
4245
  if (sessionState?.workingDocument) {
4093
4246
  rebuildProjectionFromDocument(state, options, sessionState.workingDocument, sessionState.selection);
@@ -4112,6 +4265,7 @@ async function closePrefabStageLifecycleScopes(state, stage) {
4112
4265
  function selectPrefabStageItem(state, itemId) {
4113
4266
  if (!state.prefabStage || state.prefabStage.selectedItemId === itemId)
4114
4267
  return false;
4268
+ advanceHierarchyProjectionRevision(state);
4115
4269
  state.prefabStage = {
4116
4270
  ...state.prefabStage,
4117
4271
  selectedItemId: itemId,
@@ -4177,6 +4331,7 @@ function dispatchPrefabStageProjectionSelectionCommand(state, options, command)
4177
4331
  if (command.type === 'selection.clear') {
4178
4332
  if (state.prefabStage.selectedItemId === null)
4179
4333
  return false;
4334
+ advanceHierarchyProjectionRevision(state);
4180
4335
  state.prefabStage = { ...state.prefabStage, selectedItemId: null };
4181
4336
  state.editorStateRevision += 1;
4182
4337
  syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-clear', state.editorStateRevision);
@@ -4193,6 +4348,7 @@ function dispatchPrefabStageProjectionSelectionCommand(state, options, command)
4193
4348
  const currentlySelected = currentProjectionSelection.selectedIds.includes(candidateId);
4194
4349
  if ((command.type === 'selection.toggle' && currentlySelected)
4195
4350
  || (command.type === 'selection.remove' && currentlySelected)) {
4351
+ advanceHierarchyProjectionRevision(state);
4196
4352
  state.prefabStage = { ...state.prefabStage, selectedItemId: null };
4197
4353
  state.editorStateRevision += 1;
4198
4354
  syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-remove', state.editorStateRevision);
@@ -4203,6 +4359,7 @@ function dispatchPrefabStageProjectionSelectionCommand(state, options, command)
4203
4359
  const nextItemId = target.itemId ?? `prefab-node:${target.targetId}`;
4204
4360
  if (state.prefabStage.selectedItemId === nextItemId)
4205
4361
  return false;
4362
+ advanceHierarchyProjectionRevision(state);
4206
4363
  state.prefabStage = { ...state.prefabStage, selectedItemId: nextItemId };
4207
4364
  state.editorStateRevision += 1;
4208
4365
  syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-command', state.editorStateRevision);
@@ -5850,6 +6007,7 @@ function cancelEditorIntent(state, options) {
5850
6007
  }
5851
6008
  if (state.prefabStage?.selectedItemId) {
5852
6009
  cancelActiveOperation(state);
6010
+ advanceHierarchyProjectionRevision(state);
5853
6011
  state.prefabStage = { ...state.prefabStage, selectedItemId: null };
5854
6012
  state.editorStateRevision += 1;
5855
6013
  syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-cancel', state.editorStateRevision);
@@ -5958,24 +6116,6 @@ function createProjectionBoundsFocusFrame(bounds) {
5958
6116
  radius,
5959
6117
  };
5960
6118
  }
5961
- function frameSelectedViewportProjection(state, options) {
5962
- if (state.mode !== 'editor')
5963
- return false;
5964
- if (state.prefabStage) {
5965
- const activeId = getCurrentPrefabStageProjectionSelection(state, options).activeId;
5966
- if (!activeId) {
5967
- state.status = 'Focus failed: no Prefab Stage selection';
5968
- return false;
5969
- }
5970
- return focusPrefabStageProjectionNode(state, options, activeId, 'prefab-stage-frame-selection');
5971
- }
5972
- const activeId = state.session?.getState({ cloneDocuments: false }).selection.activeId ?? null;
5973
- if (!activeId) {
5974
- state.status = 'Focus failed: no selection';
5975
- return false;
5976
- }
5977
- return frameViewportProjectionNode(state, activeId);
5978
- }
5979
6119
  function focusPrefabStageProjectionNode(state, options, projectionNodeId, reason) {
5980
6120
  const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
5981
6121
  if (!state.prefabStage || !document)
@@ -6412,6 +6552,18 @@ function applyPreviewVisibilityStateToProjection(state) {
6412
6552
  projection.setPreviewVisibilityOverrides(overrides);
6413
6553
  }
6414
6554
  }
6555
+ function switchPreviewVisibilityScope(state, nextScopeId) {
6556
+ if (state.previewVisibilityScopeId === nextScopeId)
6557
+ return;
6558
+ state.previewVisibilityByScope.set(state.previewVisibilityScopeId, state.previewVisibility);
6559
+ const next = state.previewVisibilityByScope.get(nextScopeId) ?? {
6560
+ overrides: {},
6561
+ isolatedObjectIds: [],
6562
+ };
6563
+ state.previewVisibilityByScope.set(nextScopeId, next);
6564
+ state.previewVisibilityScopeId = nextScopeId;
6565
+ state.previewVisibility = next;
6566
+ }
6415
6567
  function setSceneCameraPreviewEnabled(state, options, enabled) {
6416
6568
  if (!enabled) {
6417
6569
  state.sceneCameraPreviewEnabled = false;
@@ -6634,6 +6786,7 @@ function createUiState(state, options) {
6634
6786
  const hierarchy = document
6635
6787
  ? createMarkerGraphHierarchyItems(documentHierarchy, markerGraph, createMarkerGraphHierarchyVisibility(state))
6636
6788
  : [];
6789
+ const prefabStage = createBrowserPrefabStageState(state, options, document);
6637
6790
  state.performanceObjectCount = document ? countLocalEditorAuthoredObjects(documentHierarchy) : null;
6638
6791
  state.performanceStats = createLocalEditorPerformanceStats({
6639
6792
  frameStats: state.sceneFrameStats,
@@ -6644,7 +6797,7 @@ function createUiState(state, options) {
6644
6797
  ...state.assets.map(asset => toBrowserAssetItem(options, asset)),
6645
6798
  ...documentAssetItems,
6646
6799
  ]);
6647
- return {
6800
+ const uiState = {
6648
6801
  mode: state.mode,
6649
6802
  busy: state.busy,
6650
6803
  agentBridge: state.agentBridgeStatus,
@@ -6659,6 +6812,8 @@ function createUiState(state, options) {
6659
6812
  hierarchy,
6660
6813
  selectedIds,
6661
6814
  activeId,
6815
+ documentRevision: sessionState?.documentRevision ?? 0,
6816
+ hierarchyProjectionRevision: state.hierarchyProjectionRevision,
6662
6817
  selectionSummary: {
6663
6818
  count: selectedIds.length,
6664
6819
  activeId,
@@ -6705,7 +6860,7 @@ function createUiState(state, options) {
6705
6860
  enabled: state.sceneCameraPreviewEnabled,
6706
6861
  available: hasSceneCameraPreviewRig(state, options),
6707
6862
  },
6708
- prefabStage: createBrowserPrefabStageState(state, options, document),
6863
+ prefabStage,
6709
6864
  grid: {
6710
6865
  visible: state.gridVisible,
6711
6866
  available: !!state.grid,
@@ -6720,6 +6875,74 @@ function createUiState(state, options) {
6720
6875
  }
6721
6876
  : null,
6722
6877
  };
6878
+ const hierarchyProjection = createLocalEditorHierarchyProjection(createLocalEditorActiveHierarchyScope(uiState));
6879
+ uiState.hierarchyProjection = hierarchyProjection;
6880
+ uiState.statusDiagnostics = [
6881
+ ...hierarchyProjection.diagnostics,
6882
+ ...state.hierarchyReconcileDiagnostics,
6883
+ ...state.hierarchyCommandDiagnostics,
6884
+ ];
6885
+ uiState.statusDiagnostics = resolveLocalEditorStatusDiagnostics(uiState);
6886
+ if (!state.prefabStage) {
6887
+ uiState.selectedIds = hierarchyProjection.selectedIds;
6888
+ uiState.activeId = hierarchyProjection.activeId;
6889
+ uiState.selectionSummary = {
6890
+ count: hierarchyProjection.selectedIds.length,
6891
+ activeId: hierarchyProjection.activeId,
6892
+ };
6893
+ if (activeId !== hierarchyProjection.activeId) {
6894
+ uiState.inspectorObject = null;
6895
+ uiState.inspectorMultiObject = null;
6896
+ }
6897
+ }
6898
+ return uiState;
6899
+ }
6900
+ function reconcileHierarchySelectionBeforeRender(state, options) {
6901
+ const sessionState = state.session?.getState({ cloneDocuments: false });
6902
+ const document = sessionState?.workingDocument ?? null;
6903
+ if (!sessionState || !document)
6904
+ return;
6905
+ if (state.prefabStage) {
6906
+ const stage = createBrowserPrefabStageState(state, options, document);
6907
+ if (!stage)
6908
+ return;
6909
+ const projection = createLocalEditorHierarchyProjection(createLocalEditorPrefabStageHierarchyScope(stage, sessionState.documentRevision, state.hierarchyProjectionRevision));
6910
+ if (stage.selectedItemId === projection.activeId)
6911
+ return;
6912
+ const reconcileDiagnostics = projection.diagnostics.filter(diagnostic => diagnostic.code === 'hierarchy-selection-stale');
6913
+ advanceHierarchyProjectionRevision(state);
6914
+ state.hierarchyReconcileDiagnostics = reconcileDiagnostics;
6915
+ state.prefabStage.selectedItemId = projection.activeId;
6916
+ state.editorStateRevision += 1;
6917
+ syncPrefabStageSelectionToProjection(state, options, 'hierarchy-projection-selection-sanitize', state.editorStateRevision);
6918
+ return;
6919
+ }
6920
+ const documentHierarchy = options.documentAdapter.getHierarchyItems(document);
6921
+ const markerGraph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
6922
+ const hierarchy = markerGraph
6923
+ ? createMarkerGraphHierarchyItems(documentHierarchy, markerGraph, createMarkerGraphHierarchyVisibility(state))
6924
+ : documentHierarchy;
6925
+ const projection = createLocalEditorHierarchyProjection(createLocalEditorSceneHierarchyScope({
6926
+ hierarchy,
6927
+ selectedIds: sessionState.selection.selectedIds,
6928
+ activeId: sessionState.selection.activeId,
6929
+ documentRevision: sessionState.documentRevision,
6930
+ hierarchyProjectionRevision: state.hierarchyProjectionRevision,
6931
+ }));
6932
+ if (projection.activeId === sessionState.selection.activeId
6933
+ && projection.selectedIds.length === sessionState.selection.selectedIds.length
6934
+ && projection.selectedIds.every((id, index) => id === sessionState.selection.selectedIds[index]))
6935
+ return;
6936
+ const reconcileDiagnostics = projection.diagnostics.filter(diagnostic => diagnostic.code === 'hierarchy-selection-stale');
6937
+ state.session?.dispatch({
6938
+ type: 'selection.replace',
6939
+ selectedIds: projection.selectedIds,
6940
+ activeId: projection.activeId,
6941
+ label: 'Sanitize Hierarchy Projection Selection',
6942
+ });
6943
+ state.hierarchyReconcileDiagnostics = reconcileDiagnostics;
6944
+ state.editorStateRevision += 1;
6945
+ syncSelectionToProjection(state, options, { selectedIds: projection.selectedIds, activeId: projection.activeId }, 'hierarchy-projection-selection-sanitize', state.editorStateRevision);
6723
6946
  }
6724
6947
  function createBrowserPrefabStageState(state, options, document) {
6725
6948
  const stage = state.prefabStage;