@volter/editor-core 0.5.57
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/BUNDLED_NOTICES +1527 -0
- package/LICENSE +686 -0
- package/LICENSE-APACHE +202 -0
- package/NOTICE +18 -0
- package/README.md +24 -0
- package/dist/build/chunk-JS4D4XZ3.js +175 -0
- package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
- package/dist/build/vite-plugin-product-contributions.js +100 -0
- package/dist/build/vite-plugin-product-contributions.js.map +7 -0
- package/dist/build/vite-plugin-shared-react.js +25 -0
- package/dist/build/vite-plugin-shared-react.js.map +7 -0
- package/dist/build/vite-plugin-shared-three.js +86 -0
- package/dist/build/vite-plugin-shared-three.js.map +7 -0
- package/dist/server/chunk-KMJC7AGO.js +98 -0
- package/dist/server/chunk-KMJC7AGO.js.map +7 -0
- package/dist/server/chunk-PSILUMGS.js +46 -0
- package/dist/server/chunk-PSILUMGS.js.map +7 -0
- package/dist/server/frame-proxy.js +255 -0
- package/dist/server/frame-proxy.js.map +7 -0
- package/dist/server/open-browser.js +53 -0
- package/dist/server/open-browser.js.map +7 -0
- package/dist/server/process-shutdown.js +127 -0
- package/dist/server/process-shutdown.js.map +7 -0
- package/dist/server/session-registry.js +141 -0
- package/dist/server/session-registry.js.map +7 -0
- package/dist/server/spawn-opener.js +11 -0
- package/dist/server/spawn-opener.js.map +7 -0
- package/dist/server/worktree-identity.js +11 -0
- package/dist/server/worktree-identity.js.map +7 -0
- package/dist-server/packaged.mjs +88816 -0
- package/package.json +118 -0
- package/scripts/build-plugins.mjs +14 -0
- package/scripts/build-server.mjs +26 -0
- package/scripts/build-session.mjs +9 -0
- package/server/account-credentials.ts +60 -0
- package/server/account-service.ts +1434 -0
- package/server/adapter-region-includes.ts +47 -0
- package/server/asset-catalog-v2.ts +175 -0
- package/server/asset-history-snapshots.ts +196 -0
- package/server/asset-ledger-store.ts +195 -0
- package/server/asset-library-routes.ts +1595 -0
- package/server/blender-wasm-artifact.ts +430 -0
- package/server/boot-timings.ts +106 -0
- package/server/canonical-path.ts +50 -0
- package/server/checkout-workspace-preflight.ts +349 -0
- package/server/cloud-asset-catalog.ts +158 -0
- package/server/coding-inference-launch.ts +237 -0
- package/server/collaboration-account-client.ts +202 -0
- package/server/collaboration-attribution.ts +122 -0
- package/server/collaboration-session.ts +934 -0
- package/server/comfyui-bridge.ts +248 -0
- package/server/console-ledger.ts +499 -0
- package/server/creation-site-transform.ts +304 -0
- package/server/creation-site-write.ts +648 -0
- package/server/data-file-serialize.ts +158 -0
- package/server/editor-brand-html.ts +72 -0
- package/server/editor-control-socket.ts +366 -0
- package/server/editor-server-options.ts +190 -0
- package/server/editor-server.ts +1483 -0
- package/server/editor-sse.ts +352 -0
- package/server/engine-provenance.ts +169 -0
- package/server/engine-source-restart.ts +117 -0
- package/server/frame-bridge.ts +115 -0
- package/server/frame-proxy.ts +490 -0
- package/server/frame-workbench.ts +259 -0
- package/server/frontend-handoff.ts +229 -0
- package/server/game-globals-shadow.ts +113 -0
- package/server/gameplay-session-retention.ts +149 -0
- package/server/gameplay-sessions.ts +123 -0
- package/server/generation-jobs.ts +201 -0
- package/server/generation-reconciler.ts +137 -0
- package/server/generative-execution-context.ts +59 -0
- package/server/git-workflow.ts +612 -0
- package/server/harness-chat-caller.ts +36 -0
- package/server/harness-chat-service.ts +1681 -0
- package/server/idle-shutdown.ts +170 -0
- package/server/js-profiling-policy.ts +52 -0
- package/server/launcher-settings.ts +84 -0
- package/server/local-asset-catalog.ts +410 -0
- package/server/managed-account-defaults.ts +22 -0
- package/server/mock-control-plane.ts +540 -0
- package/server/model-import-conversion.ts +345 -0
- package/server/native-credential-store.ts +162 -0
- package/server/open-browser.ts +118 -0
- package/server/packaged.ts +1192 -0
- package/server/play-stall.ts +126 -0
- package/server/process-shutdown.ts +218 -0
- package/server/product-presets.ts +55 -0
- package/server/project-build-artifact.ts +95 -0
- package/server/project-components.ts +100 -0
- package/server/project-dependency-invalidation.ts +114 -0
- package/server/project-file-scan.ts +235 -0
- package/server/project-hmr-files.ts +215 -0
- package/server/project-install-roots.ts +210 -0
- package/server/project-kinds.ts +71 -0
- package/server/project-mcp-servers.ts +82 -0
- package/server/project-module-freshness.ts +109 -0
- package/server/project-module-instance.ts +375 -0
- package/server/project-optimize-deps-entries.ts +827 -0
- package/server/project-output-writer.ts +679 -0
- package/server/project-package-origin.ts +63 -0
- package/server/project-root-surface.ts +831 -0
- package/server/project-scratch-path.ts +47 -0
- package/server/project-script-hmr.ts +619 -0
- package/server/project-serving-plugins.ts +236 -0
- package/server/project-tools.ts +653 -0
- package/server/project-validation.ts +250 -0
- package/server/project-verbs.ts +127 -0
- package/server/project-view.ts +52 -0
- package/server/project-watch.ts +1134 -0
- package/server/project-work-coordinator.ts +288 -0
- package/server/provider-credentials.ts +331 -0
- package/server/recent-projects-store.ts +98 -0
- package/server/redact-secrets.ts +71 -0
- package/server/repository-presence.ts +267 -0
- package/server/routes/account.ts +357 -0
- package/server/routes/agents.ts +60 -0
- package/server/routes/assets.ts +154 -0
- package/server/routes/build.ts +13 -0
- package/server/routes/collaboration.ts +595 -0
- package/server/routes/configurations.ts +419 -0
- package/server/routes/context.ts +264 -0
- package/server/routes/control-plane.ts +1811 -0
- package/server/routes/logs.ts +272 -0
- package/server/routes/project-identity.ts +302 -0
- package/server/routes/project-open.ts +429 -0
- package/server/routes/project-source.ts +686 -0
- package/server/routes/project-state.ts +383 -0
- package/server/routes/relay.ts +1029 -0
- package/server/routes/route-helpers.ts +29 -0
- package/server/routes/served-modules.ts +138 -0
- package/server/routes/session-tabs.ts +196 -0
- package/server/routes/settings.ts +121 -0
- package/server/routes/share-control.ts +486 -0
- package/server/routes/themes.ts +116 -0
- package/server/routes/tools.ts +230 -0
- package/server/routes/worktrees.ts +595 -0
- package/server/scoped-game-css.ts +461 -0
- package/server/server-utils.ts +1194 -0
- package/server/session-product.ts +112 -0
- package/server/session-registry.ts +230 -0
- package/server/share-claims.ts +138 -0
- package/server/share-host.ts +526 -0
- package/server/share-session-gateway.ts +1362 -0
- package/server/share-tunnel.ts +123 -0
- package/server/spawn-opener.ts +74 -0
- package/server/support/play/log-format.ts +76 -0
- package/server/support/play/session-record.ts +199 -0
- package/server/support/project/build-discipline.ts +718 -0
- package/server/support/project/inspection-node.ts +119 -0
- package/server/support/project/log-naming.ts +54 -0
- package/server/support/project/provenance.ts +290 -0
- package/server/support/project/run-name.ts +18 -0
- package/server/support/project/session-journal.ts +830 -0
- package/server/support/project/shared.ts +327 -0
- package/server/tab-bootstrap.ts +62 -0
- package/server/tab-heartbeat.ts +477 -0
- package/server/tab-lifecycle.ts +550 -0
- package/server/tab-presence.ts +1508 -0
- package/server/team-agent-mirror.ts +95 -0
- package/server/twin-auth.ts +138 -0
- package/server/vendored-lock-recorder.ts +968 -0
- package/server/worktree-identity.ts +121 -0
- package/server/worktree-management.ts +398 -0
- package/server/worktree-retention.d.mts +13 -0
- package/server/worktree-retention.mjs +205 -0
- package/src/EditorContext.tsx +169 -0
- package/src/account.ts +333 -0
- package/src/action-registry.ts +532 -0
- package/src/active-product.ts +74 -0
- package/src/active-project.ts +155 -0
- package/src/adapter-editor-config.ts +25 -0
- package/src/adapter-observation.ts +49 -0
- package/src/animation/stage-transport.ts +379 -0
- package/src/animation/three-clips-subject.ts +172 -0
- package/src/api/asset-library-wire.ts +45 -0
- package/src/api/assets.ts +365 -0
- package/src/api/base.ts +10 -0
- package/src/api/build.ts +99 -0
- package/src/api/git-wire.ts +56 -0
- package/src/api/logs.ts +92 -0
- package/src/api/project-identity.ts +74 -0
- package/src/api/project-open.ts +355 -0
- package/src/api/project-source.ts +162 -0
- package/src/api/project-state.ts +107 -0
- package/src/api/relay.ts +268 -0
- package/src/api/settings.ts +36 -0
- package/src/api/themes.ts +45 -0
- package/src/api/worktrees.ts +257 -0
- package/src/asset-compare-core.ts +171 -0
- package/src/asset-compare.ts +294 -0
- package/src/asset-editor-context.tsx +100 -0
- package/src/asset-events.ts +97 -0
- package/src/asset-inspector-actions.ts +87 -0
- package/src/asset-preview-framing.ts +357 -0
- package/src/asset-preview.ts +2802 -0
- package/src/asset-selection-viewer-registry.ts +113 -0
- package/src/asset-selection.ts +133 -0
- package/src/asset-workflow/asset-capabilities.ts +344 -0
- package/src/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/asset-workflow/asset-ledger.ts +156 -0
- package/src/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/asset-workflow/asset-types.ts +142 -0
- package/src/asset-workflow/asset-workflow-quality.ts +61 -0
- package/src/asset-workflow/audio-preview-player.ts +193 -0
- package/src/asset-workflow/audio-waveform.ts +22 -0
- package/src/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/asset-workflow/folder-preview.ts +345 -0
- package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/asset-workflow/image-view-scale.ts +32 -0
- package/src/asset-workflow/import-contract.ts +124 -0
- package/src/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/asset-workflow/model-inspection.ts +818 -0
- package/src/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +139 -0
- package/src/asset-workflow/project-asset-operations.ts +154 -0
- package/src/asset-workflow/project-asset-roots.ts +68 -0
- package/src/asset-workflow/project-content.ts +288 -0
- package/src/asset-workflow/project-source-index.ts +550 -0
- package/src/asset-workflow/thumbnail-system.ts +256 -0
- package/src/authoring/active-adapter.ts +199 -0
- package/src/authoring/active-systems.ts +422 -0
- package/src/authoring/adapter-key.ts +18 -0
- package/src/authoring/authoring-asset-url.ts +27 -0
- package/src/authoring/bootstrap-state.ts +49 -0
- package/src/authoring/boundary-authoring-adapter.ts +184 -0
- package/src/authoring/breakpoint-state.ts +43 -0
- package/src/authoring/canvas-scene-guides.ts +76 -0
- package/src/authoring/component-instance-root.ts +171 -0
- package/src/authoring/composite-authoring-adapter.ts +2110 -0
- package/src/authoring/consumer-actions.ts +520 -0
- package/src/authoring/css-numeric-style.ts +97 -0
- package/src/authoring/design-time-layers.ts +849 -0
- package/src/authoring/design-time-mount-registry.ts +235 -0
- package/src/authoring/design-time-settle.ts +343 -0
- package/src/authoring/edit-mode-authoring.ts +619 -0
- package/src/authoring/eyedropper-session.ts +60 -0
- package/src/authoring/instance-source-menu-register.ts +13 -0
- package/src/authoring/instance-source-menu.ts +135 -0
- package/src/authoring/layered-pick.ts +183 -0
- package/src/authoring/live-gesture-lock.ts +50 -0
- package/src/authoring/live-object-transform.ts +62 -0
- package/src/authoring/mount-failure-report.ts +154 -0
- package/src/authoring/mounted-root-subjects.ts +144 -0
- package/src/authoring/no-authoring-adapter.ts +55 -0
- package/src/authoring/null-inspection-subjects.tsx +75 -0
- package/src/authoring/object3d-document-persistence.ts +106 -0
- package/src/authoring/object3d-document-session-registry.ts +120 -0
- package/src/authoring/object3d-document-session.ts +1326 -0
- package/src/authoring/object3d-gesture-controller.ts +113 -0
- package/src/authoring/panel-authoring.ts +121 -0
- package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
- package/src/authoring/prefab-instance-section-model.ts +55 -0
- package/src/authoring/project-authoring-session.ts +105 -0
- package/src/authoring/provenance.ts +99 -0
- package/src/authoring/quarks-particle-systems.ts +19 -0
- package/src/authoring/react-canvas-navigation.ts +255 -0
- package/src/authoring/react-design-canvas-style.ts +20 -0
- package/src/authoring/react-story-board.ts +937 -0
- package/src/authoring/selection-scope.ts +195 -0
- package/src/authoring/shell-document-ops.ts +169 -0
- package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
- package/src/authoring/shell-viewport-policy.ts +48 -0
- package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
- package/src/authoring/stories-scope.ts +35 -0
- package/src/authoring/story-board-chrome-fit.ts +107 -0
- package/src/authoring/story-board-presentation.ts +111 -0
- package/src/authoring/three-projection-core.ts +226 -0
- package/src/authoring/viewport-pick-context.ts +40 -0
- package/src/authoring/viewport-raycast.ts +240 -0
- package/src/authoring/viewport-tool-context.ts +73 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +152 -0
- package/src/authoring/world-pan-state.ts +198 -0
- package/src/authoring/world-session-state.ts +101 -0
- package/src/authoring/write-pipe.ts +173 -0
- package/src/availability-tick.ts +66 -0
- package/src/bitmap-label.ts +120 -0
- package/src/blender-tab-metrics.ts +161 -0
- package/src/board-open-actions.ts +20 -0
- package/src/boot-routing.ts +391 -0
- package/src/build-identity.ts +15 -0
- package/src/bytes-codec.ts +62 -0
- package/src/camera-authoring.ts +175 -0
- package/src/cancellation-reason.ts +58 -0
- package/src/canvas-preview-frames.ts +482 -0
- package/src/capture-camera-pose.ts +77 -0
- package/src/chrome-registry.ts +159 -0
- package/src/chrome-slot-registry.ts +91 -0
- package/src/collaboration-client.ts +264 -0
- package/src/collaboration-presence.ts +41 -0
- package/src/command-dispatch.ts +19 -0
- package/src/command-listener.ts +2743 -0
- package/src/command-registry.ts +70 -0
- package/src/component-board-registry.ts +205 -0
- package/src/component-states-registry.ts +200 -0
- package/src/components/AgentPresentationNotice.tsx +75 -0
- package/src/components/AlignToolbar.tsx +204 -0
- package/src/components/AppRoot.tsx +394 -0
- package/src/components/ApplicationMenus.tsx +415 -0
- package/src/components/AssetBrowser.tsx +2843 -0
- package/src/components/AssetEditorShell.tsx +216 -0
- package/src/components/AssetInspectorToolSection.tsx +124 -0
- package/src/components/BoardRulers.tsx +354 -0
- package/src/components/CameraInfo.tsx +81 -0
- package/src/components/CanvasSceneViewport.tsx +556 -0
- package/src/components/CapabilityCoverageSection.tsx +185 -0
- package/src/components/CenterDocuments.tsx +22 -0
- package/src/components/ChromeSlot.tsx +20 -0
- package/src/components/CodeView.tsx +470 -0
- package/src/components/CollaborationHeaderControl.css +74 -0
- package/src/components/CompactInspectorCard.tsx +190 -0
- package/src/components/CompactInspectorShell.tsx +39 -0
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +270 -0
- package/src/components/DefaultEditorLayout.tsx +106 -0
- package/src/components/DocumentHeaderStrip.tsx +96 -0
- package/src/components/DocumentShelfRail.tsx +45 -0
- package/src/components/DocumentThumbnail.tsx +345 -0
- package/src/components/EditorLeaseGuard.tsx +372 -0
- package/src/components/ErrorBanner.tsx +123 -0
- package/src/components/FolderPreviewTile.tsx +159 -0
- package/src/components/GameHierarchy.tsx +3842 -0
- package/src/components/GameplaySessionTimeline.tsx +295 -0
- package/src/components/InspectionProjection.tsx +968 -0
- package/src/components/Inspector.tsx +233 -0
- package/src/components/InspectorCanvasPreview.tsx +35 -0
- package/src/components/InspectorFieldsSection.tsx +290 -0
- package/src/components/InspectorObjectPreview.tsx +57 -0
- package/src/components/InspectorStoriesSection.tsx +92 -0
- package/src/components/InspectorToolSection.tsx +96 -0
- package/src/components/InspectorTransformSection.tsx +245 -0
- package/src/components/LightExplorerPanel.tsx +433 -0
- package/src/components/MediaProperties.tsx +145 -0
- package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
- package/src/components/Object3DDocumentToolbar.css +100 -0
- package/src/components/Object3DDocumentToolbar.tsx +454 -0
- package/src/components/Object3DDocumentViewport.tsx +58 -0
- package/src/components/ProjectHeader.css +617 -0
- package/src/components/ProjectHeader.tsx +328 -0
- package/src/components/ProjectLayout.tsx +249 -0
- package/src/components/ReactCanvasControls.tsx +284 -0
- package/src/components/RootSelectionOverlay.tsx +3162 -0
- package/src/components/RootTextEditor.tsx +79 -0
- package/src/components/SaveStatus.tsx +70 -0
- package/src/components/StageHost.tsx +1954 -0
- package/src/components/StageOverlays.tsx +21 -0
- package/src/components/StartupErrorScreen.tsx +76 -0
- package/src/components/StartupLoadingScreen.tsx +65 -0
- package/src/components/StatsOverlay.tsx +78 -0
- package/src/components/SurfaceStateOverlay.tsx +24 -0
- package/src/components/ToolContributionSurfaces.tsx +65 -0
- package/src/components/ToolHost.tsx +370 -0
- package/src/components/ToolObject3DPreview.tsx +39 -0
- package/src/components/Toolbar.tsx +595 -0
- package/src/components/TransientHint.tsx +44 -0
- package/src/components/TransportStrip.tsx +174 -0
- package/src/components/VersionControlSection.tsx +470 -0
- package/src/components/VgaiLogo.css +83 -0
- package/src/components/VgaiLogo.tsx +35 -0
- package/src/components/ViewportControlsHint.tsx +60 -0
- package/src/components/ViewportFurniture.tsx +401 -0
- package/src/components/ViewportOverlay.tsx +145 -0
- package/src/components/ViewportOverlaysMenu.tsx +153 -0
- package/src/components/ViewportShadingMenu.tsx +300 -0
- package/src/components/ViewportViewMenu.tsx +101 -0
- package/src/components/WorkspaceDocumentSurface.tsx +204 -0
- package/src/components/WorkspaceUtilitySurface.tsx +22 -0
- package/src/components/WorktreeSwitcher.css +239 -0
- package/src/components/WorktreeSwitcher.tsx +890 -0
- package/src/components/account-documents.tsx +1162 -0
- package/src/components/asset-documents.tsx +812 -0
- package/src/components/asset-editor-persistence.ts +216 -0
- package/src/components/asset-selection-section.tsx +535 -0
- package/src/components/asset-thumbnails.tsx +312 -0
- package/src/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
- package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
- package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
- package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
- package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
- package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
- package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
- package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
- package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
- package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
- package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/components/asset-viewers/shader-source.ts +144 -0
- package/src/components/asset-workflow.css +731 -0
- package/src/components/board-guides.ts +150 -0
- package/src/components/compact-inspector.css +539 -0
- package/src/components/core-utilities.tsx +90 -0
- package/src/components/editor-notifications.css +40 -0
- package/src/components/engine-workspace.css +274 -0
- package/src/components/inspector-preview-section.tsx +238 -0
- package/src/components/inspector-property-grouping.ts +64 -0
- package/src/components/inspector-revert-label.ts +20 -0
- package/src/components/inspector-selection.ts +42 -0
- package/src/components/inspector-stories-gating.ts +171 -0
- package/src/components/inspector-transform-subject.ts +11 -0
- package/src/components/inspector-transform.ts +75 -0
- package/src/components/kind-documents.tsx +500 -0
- package/src/components/palette-action-publisher.tsx +140 -0
- package/src/components/primitives/DraftColorInput.tsx +74 -0
- package/src/components/product-shell.css +1829 -0
- package/src/components/project-tool-documents.tsx +410 -0
- package/src/components/scene-documents.tsx +223 -0
- package/src/components/stage-keyboard.tsx +37 -0
- package/src/components/stage-overlay-set.tsx +107 -0
- package/src/components/stage-presence-markers.ts +478 -0
- package/src/components/standard-viewport-dressing.ts +417 -0
- package/src/components/status-contributions.tsx +403 -0
- package/src/components/tool-documents.tsx +309 -0
- package/src/components/tool-schema-form.tsx +262 -0
- package/src/components/use-after-paint.ts +41 -0
- package/src/components/use-project-image-assets.ts +86 -0
- package/src/components/viewport-header-controls.css +90 -0
- package/src/components/viewport-surface-status.tsx +55 -0
- package/src/components/workspace-history.ts +32 -0
- package/src/components/workspace-static-panel-registry.tsx +152 -0
- package/src/components/workspace-surfaces.css +621 -0
- package/src/components/world-documents.tsx +586 -0
- package/src/components/world-overlay-gestures.ts +1694 -0
- package/src/composite-screenshot.ts +1641 -0
- package/src/console-sync.ts +131 -0
- package/src/constraint-helper.ts +338 -0
- package/src/content-entry-source-registry.ts +184 -0
- package/src/coverage/authoring-seam-evidence.ts +300 -0
- package/src/coverage/canvas-reveal.ts +192 -0
- package/src/coverage/design-time-surfaces.ts +101 -0
- package/src/coverage/live-seam-evidence.ts +11 -0
- package/src/coverage/ontology-invariants.ts +466 -0
- package/src/coverage/session-vitals.ts +501 -0
- package/src/coverage/system-seam-evidence.ts +72 -0
- package/src/crash-null-boundary.ts +36 -0
- package/src/creation-site-edit.ts +1479 -0
- package/src/creation-site-registry.ts +160 -0
- package/src/delegate-harness-registry.ts +143 -0
- package/src/document-context-registry.ts +177 -0
- package/src/document-open-registry.ts +200 -0
- package/src/document-preview-source.ts +20 -0
- package/src/document-renderer-session.ts +138 -0
- package/src/editor-api.ts +46 -0
- package/src/editor-chrome-capture.ts +139 -0
- package/src/editor-commands.ts +164 -0
- package/src/editor-console.ts +563 -0
- package/src/editor-current-view.ts +82 -0
- package/src/editor-document-probe.ts +881 -0
- package/src/editor-git-client.ts +115 -0
- package/src/editor-host-door.ts +462 -0
- package/src/editor-hotkeys.ts +842 -0
- package/src/editor-lease-view.ts +39 -0
- package/src/editor-lease.ts +415 -0
- package/src/editor-mode.ts +19 -0
- package/src/editor-notifications.ts +140 -0
- package/src/editor-presence.ts +563 -0
- package/src/editor-presentation-activity.ts +50 -0
- package/src/editor-presentation-notice.ts +42 -0
- package/src/editor-runtime.tsx +145 -0
- package/src/editor-server-response.ts +86 -0
- package/src/editor-session-attribution.ts +75 -0
- package/src/editor-session-mode.ts +54 -0
- package/src/editor-shell-store.ts +1304 -0
- package/src/editor-state-facets.ts +74 -0
- package/src/editor-styles.css +33 -0
- package/src/editor-view-presentation.ts +810 -0
- package/src/editor-viewport.ts +5302 -0
- package/src/entity-lod.ts +31 -0
- package/src/entity-object.ts +91 -0
- package/src/files/file-provider.ts +62 -0
- package/src/files/project-files.ts +264 -0
- package/src/finders/index.ts +136 -0
- package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/frame/bridge.tsx +1408 -0
- package/src/frame/product.ts +77 -0
- package/src/gameplay-dom-recording.ts +319 -0
- package/src/gameplay-export-state.ts +14 -0
- package/src/gameplay-replay.ts +417 -0
- package/src/gameplay-session-time.ts +9 -0
- package/src/gameplay-sessions.ts +204 -0
- package/src/harness-chat-types.ts +253 -0
- package/src/hierarchy-component-marks.ts +298 -0
- package/src/hierarchy-drop.ts +91 -0
- package/src/hierarchy-expansion-state.ts +80 -0
- package/src/hierarchy-header-slot.ts +52 -0
- package/src/hierarchy-internals.ts +197 -0
- package/src/hierarchy-kind-icon.ts +217 -0
- package/src/hierarchy-mark-reader.ts +73 -0
- package/src/hierarchy-menu-registry.ts +67 -0
- package/src/hierarchy-node-rows.ts +307 -0
- package/src/hierarchy-panel-view.ts +280 -0
- package/src/hierarchy-projection.ts +76 -0
- package/src/hierarchy-row-cache.ts +243 -0
- package/src/hierarchy-row-model.ts +308 -0
- package/src/hierarchy-rows.ts +11 -0
- package/src/hierarchy-walk.ts +86 -0
- package/src/history/editor-session.ts +25 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-delegate.ts +163 -0
- package/src/history/history-limit-notices.ts +43 -0
- package/src/history/history-service.ts +1173 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +386 -0
- package/src/history/project-root-history-backends.ts +139 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +103 -0
- package/src/history/source-history-backend.ts +546 -0
- package/src/history/types.ts +124 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hotkeys.ts +188 -0
- package/src/inference-diagnostics.ts +69 -0
- package/src/initial-project.ts +80 -0
- package/src/inspection/active-subject.ts +578 -0
- package/src/inspection/active-surface.ts +142 -0
- package/src/inspection/compose.ts +1064 -0
- package/src/inspection/display.ts +170 -0
- package/src/inspection/document-subject.ts +109 -0
- package/src/inspection/game-subject.ts +85 -0
- package/src/inspection/model.ts +542 -0
- package/src/inspection/null-subject.ts +115 -0
- package/src/inspection/serialize.ts +357 -0
- package/src/inspection/use-active-inspection.ts +180 -0
- package/src/inspector-presentation.ts +201 -0
- package/src/inspector-section-registry.ts +221 -0
- package/src/instance-source-actions.ts +163 -0
- package/src/instanced-presentation.ts +164 -0
- package/src/js-heap.ts +71 -0
- package/src/key-actions.ts +91 -0
- package/src/keymap-presets.ts +401 -0
- package/src/layout-policy.ts +31 -0
- package/src/learn-links.ts +73 -0
- package/src/light-explorer-model.ts +134 -0
- package/src/live-canvas-frame.ts +55 -0
- package/src/live-document.ts +279 -0
- package/src/live-module-source.ts +230 -0
- package/src/live-session-registry.ts +220 -0
- package/src/live-transition.ts +633 -0
- package/src/manifest-project.ts +107 -0
- package/src/model-thumbnail.ts +523 -0
- package/src/native-selection-style.ts +202 -0
- package/src/object3d-document-write-policy.ts +137 -0
- package/src/packaged-runtime.ts +108 -0
- package/src/palettes/maya.palette.json +57 -0
- package/src/palettes/substance.palette.json +57 -0
- package/src/pasteboard-module.ts +81 -0
- package/src/performance-profiler.ts +367 -0
- package/src/performance-sources.ts +69 -0
- package/src/play-boot-phase.ts +145 -0
- package/src/presentation-surface.ts +248 -0
- package/src/project-adapter.ts +1143 -0
- package/src/project-asset-refresh.ts +26 -0
- package/src/project-declaration-refresh.ts +31 -0
- package/src/project-local-state.ts +118 -0
- package/src/project-manager.ts +243 -0
- package/src/project-module-changes.ts +163 -0
- package/src/project-module-split.ts +266 -0
- package/src/project-provenance.ts +115 -0
- package/src/project-ready.ts +42 -0
- package/src/project-session-reset.ts +67 -0
- package/src/project-shape.ts +68 -0
- package/src/project-tool-discovery.ts +66 -0
- package/src/project-tools.ts +107 -0
- package/src/project-work-types.ts +149 -0
- package/src/projection/three.ts +898 -0
- package/src/projection/types.ts +44 -0
- package/src/readiness.ts +113 -0
- package/src/reflection-probe-helper.ts +142 -0
- package/src/reported-play-state.ts +90 -0
- package/src/resolve-relative-specifier.ts +33 -0
- package/src/scene-document-plan.ts +315 -0
- package/src/scene-framing.ts +315 -0
- package/src/scene-live-open.ts +210 -0
- package/src/scene-view-fog.ts +89 -0
- package/src/scoped-game-css.ts +152 -0
- package/src/session-orphan-record.ts +193 -0
- package/src/session-tombstone.ts +126 -0
- package/src/settings/settings-provider.ts +82 -0
- package/src/settings-store.ts +345 -0
- package/src/shared-view-restore.ts +42 -0
- package/src/shell-store-door.ts +45 -0
- package/src/source-conflict.ts +122 -0
- package/src/spatial-handle-visuals.ts +332 -0
- package/src/stage-context.ts +341 -0
- package/src/stage-store-registry.ts +63 -0
- package/src/stale-chunk-recovery.ts +34 -0
- package/src/state-report-deferral.ts +73 -0
- package/src/storage/host-files-storage.ts +97 -0
- package/src/storage/http-storage.ts +174 -0
- package/src/storage/index.ts +75 -0
- package/src/storage/mem-storage.ts +149 -0
- package/src/storage/path-lock.ts +44 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/types.ts +116 -0
- package/src/stories/StoryComponentThumbnail.tsx +184 -0
- package/src/stories/StoryPreviewMount.tsx +306 -0
- package/src/stories/component-content-source.tsx +106 -0
- package/src/stories/component-states-source.ts +78 -0
- package/src/stories/compose-project-stories.ts +255 -0
- package/src/stories/pixi-story-model.ts +30 -0
- package/src/stories/prefabs-finder.ts +54 -0
- package/src/stories/prefabs-from-stories.ts +182 -0
- package/src/stories/project-story-discovery.ts +78 -0
- package/src/stories/project-story-regions.ts +24 -0
- package/src/stories/story-actions.ts +54 -0
- package/src/stories/story-capture-command.ts +124 -0
- package/src/stories/story-capture.ts +584 -0
- package/src/stories/story-declared-medium.ts +126 -0
- package/src/stories/story-discovery.ts +176 -0
- package/src/stories/story-document-openers.ts +36 -0
- package/src/stories/story-dom-runtime.ts +78 -0
- package/src/stories/story-grouping.ts +111 -0
- package/src/stories/story-lane.ts +88 -0
- package/src/stories/story-mount-turn.ts +27 -0
- package/src/stories/story-opener.ts +260 -0
- package/src/stories/story-pixi-preview.ts +408 -0
- package/src/stories/story-presentation.ts +215 -0
- package/src/stories/story-registry.ts +517 -0
- package/src/stories/story-three-preview.ts +807 -0
- package/src/stories/three-story-model.ts +96 -0
- package/src/story-three-preview-runtime.ts +56 -0
- package/src/surface-keyboard.ts +112 -0
- package/src/surface-state.ts +124 -0
- package/src/tab-bootstrap.js +628 -0
- package/src/tab-census.ts +198 -0
- package/src/tab-lifecycle-client.ts +196 -0
- package/src/theme-library.ts +844 -0
- package/src/theme-preference.ts +378 -0
- package/src/theme.css +3437 -0
- package/src/three-viewport/bone-selection-highlight.ts +119 -0
- package/src/three-viewport/camera-fit.ts +41 -0
- package/src/three-viewport/interactive-renderer.ts +132 -0
- package/src/three-viewport/selection-brackets.ts +310 -0
- package/src/three-viewport/selection-outline.ts +183 -0
- package/src/three-viewport/skeleton-helper.ts +61 -0
- package/src/three-viewport/source-color.ts +167 -0
- package/src/three-viewport/studio-environment.ts +96 -0
- package/src/three-viewport-presentation.ts +23 -0
- package/src/tool-contribution-play.ts +74 -0
- package/src/tool-loader.ts +1798 -0
- package/src/transform-mode-request.ts +57 -0
- package/src/transient-hint.ts +78 -0
- package/src/trigger-volume-helper.ts +116 -0
- package/src/ui-source/adapter-region-includes.ts +241 -0
- package/src/ui-source/ensure-import.ts +132 -0
- package/src/ui-source/file-region-resolver.ts +302 -0
- package/src/ui-source/inspect.ts +775 -0
- package/src/ui-source/oid-transform.ts +1845 -0
- package/src/ui-source/plan-csf-story.ts +196 -0
- package/src/ui-source/plan-extract-component.ts +421 -0
- package/src/ui-source/plan-fork-component.ts +621 -0
- package/src/ui-source/plan-named-style.ts +95 -0
- package/src/ui-source/plan-source-edit.ts +353 -0
- package/src/ui-source/r3f-contract-resolver.ts +327 -0
- package/src/ui-source/r3f-diagnostic-index.ts +165 -0
- package/src/ui-source/r3f-environment-binding.ts +133 -0
- package/src/ui-source/r3f-joint-binding.ts +251 -0
- package/src/ui-source/r3f-lod-binding.ts +91 -0
- package/src/ui-source/r3f-particle-binding.ts +235 -0
- package/src/ui-source/r3f-physics-binding.ts +483 -0
- package/src/ui-source/r3f-project-contracts.ts +444 -0
- package/src/ui-source/relative-import-specifier.ts +34 -0
- package/src/ui-source/reparent-guard.ts +544 -0
- package/src/ui-source/source-edit-request.ts +97 -0
- package/src/ui-source/source-write-backend.ts +618 -0
- package/src/ui-source/syntactic-prop-specs.ts +296 -0
- package/src/ui-source/tier-source-write-backend.ts +279 -0
- package/src/ui-source/ts-ast.ts +171 -0
- package/src/ui-source/utility-class-support.ts +169 -0
- package/src/ui-source/write-component-default.ts +133 -0
- package/src/ui-source/writer.ts +1989 -0
- package/src/viewport-activation-timings.ts +840 -0
- package/src/viewport-authoring-policy.ts +186 -0
- package/src/viewport-controls-hint.ts +57 -0
- package/src/viewport-door.ts +237 -0
- package/src/viewport-shading-boundary.ts +12 -0
- package/src/vite-error-surface.ts +141 -0
- package/src/wait-until.ts +37 -0
- package/src/workspace-areas.ts +156 -0
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-available-documents.ts +127 -0
- package/src/workspace-core-utilities.ts +31 -0
- package/src/workspace-document-ids.ts +59 -0
- package/src/workspace-document-registry.ts +612 -0
- package/src/workspace-document-restore.ts +145 -0
- package/src/workspace-host-commands.ts +141 -0
- package/src/workspace-persistence-gate.ts +40 -0
- package/src/workspace-play-utilities.ts +44 -0
- package/src/workspace-presets.ts +425 -0
- package/src/workspace-regions.ts +294 -0
- package/src/workspace-state-persistence.ts +536 -0
- package/src/workspace-static-panels.ts +73 -0
- package/src/workspace-status-registry.ts +121 -0
- package/src/workspace-style.ts +239 -0
- package/src/workspace-utility-commands.ts +74 -0
- package/src/workspace-utility-registry.ts +263 -0
- package/src/workspace-viewport-rect.ts +97 -0
- package/src/world-adoption.ts +115 -0
- package/src/world-document-routing.ts +104 -0
- package/vite-plugin-creation-site-write.ts +184 -0
- package/vite-plugin-creation-site.ts +80 -0
- package/vite-plugin-game-static.ts +303 -0
- package/vite-plugin-module-doorways.ts +336 -0
- package/vite-plugin-product-contributions.ts +197 -0
- package/vite-plugin-project-game-static.ts +125 -0
- package/vite-plugin-project-jsx-js.ts +65 -0
- package/vite-plugin-project-root-absolute-assets.ts +359 -0
- package/vite-plugin-shared-react.ts +420 -0
- package/vite-plugin-shared-three.ts +203 -0
- package/vite-plugin-ui-oid.ts +2106 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client half of the SUPERVISED LEASE (policy: ../editor-lease.ts).
|
|
3
|
+
*
|
|
4
|
+
* A watchdog that polls the editor server's identity every few seconds, reads
|
|
5
|
+
* this page's control-channel liveness alongside it, and acts on the pair:
|
|
6
|
+
*
|
|
7
|
+
* - the session is provably GONE (failed polls AND a dropped control socket)
|
|
8
|
+
* or TAKEN OVER → tear the running game down (which drops the Colyseus room,
|
|
9
|
+
* so no ghost player is left behind) and raise the "Editor disconnected"
|
|
10
|
+
* overlay;
|
|
11
|
+
* - the polls fail but the control socket is still up → the server is BUSY,
|
|
12
|
+
* not gone. Say so in a passive banner that changes nothing and blocks
|
|
13
|
+
* nothing, and clear it the moment a poll answers.
|
|
14
|
+
*
|
|
15
|
+
* That second branch is the fix for the defect this file caused: a blocking,
|
|
16
|
+
* non-dismissable overlay eats every pointer event while `page.evaluate` keeps
|
|
17
|
+
* working, so a false void is invisible to every JS-level check and turns a
|
|
18
|
+
* 12-second hiccup into a lost session. Measured 2026-08-09: `kill -STOP` on a
|
|
19
|
+
* healthy dev server raised the scrim in ~19s with the control socket still
|
|
20
|
+
* OPEN. Nothing here may block the page on evidence that weak — and the
|
|
21
|
+
* overlay that DOES appear is dismissable ("Continue anyway") and clears
|
|
22
|
+
* itself when the same server answers again.
|
|
23
|
+
*
|
|
24
|
+
* This is the UNGRACEFUL counterpart to the graceful `tab-close` path
|
|
25
|
+
* (tab-lifecycle-client.ts): a clean `vgai close` pushes an event the tab acts
|
|
26
|
+
* on, but a killed/crashed server sends nothing.
|
|
27
|
+
*
|
|
28
|
+
* ## Why this watchdog is not (only) a timer
|
|
29
|
+
*
|
|
30
|
+
* An interval is not a heartbeat the page controls. Measured twice on live
|
|
31
|
+
* sessions, 2026-08-19: after its server was SIGKILLed, the tab made zero
|
|
32
|
+
* network attempts of any kind for the rest of the run — no poll, no socket
|
|
33
|
+
* reconnect, no worker beat — and a relaunched server on the same port went
|
|
34
|
+
* unnoticed too. Once from a hidden tab over four and a half minutes, once
|
|
35
|
+
* from a tab that was visible at the kill, over forty-five seconds. Why the
|
|
36
|
+
* page stops is not established (see ../editor-lease.ts); that it may stop is.
|
|
37
|
+
* A watchdog that can only fire from `setInterval` is therefore asleep for
|
|
38
|
+
* exactly the stretch the user is away, and still owes three polls when they
|
|
39
|
+
* come back and start clicking.
|
|
40
|
+
*
|
|
41
|
+
* So the poll is driven by the MOMENTS THAT MATTER as well as the clock: the
|
|
42
|
+
* tab becoming visible again, a `pageshow` restore, and either edge of the
|
|
43
|
+
* control socket. The clock moved into the DECISION instead — a void needs the
|
|
44
|
+
* darkness to have lasted `LEASE_MIN_DARK_WINDOW_MS`, so polling eagerly can
|
|
45
|
+
* never turn a server that is merely between processes into a dead one.
|
|
46
|
+
*
|
|
47
|
+
* And because the page may be closed or discarded while the overlay is up, the
|
|
48
|
+
* void also leaves a durable note (`session-orphan-record.ts`) that the NEXT
|
|
49
|
+
* session on this address reads and reports. The overlay is what the user in
|
|
50
|
+
* front of the tab sees; the note is what everyone else gets.
|
|
51
|
+
*
|
|
52
|
+
* Editor-only by construction: it lives behind the `/__editor` server, so a
|
|
53
|
+
* standalone `npm run game` page (which has no such server) never runs it —
|
|
54
|
+
* no game-side flag is involved.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
import { useEffect, useRef, useState } from 'react';
|
|
58
|
+
import { pollEditorLeaseIdentity } from '../editor-api';
|
|
59
|
+
import {
|
|
60
|
+
DEFAULT_LEASE_FAILURE_THRESHOLD,
|
|
61
|
+
decideLeaseRecovery,
|
|
62
|
+
decideLeaseState,
|
|
63
|
+
forgetLeaseFailures,
|
|
64
|
+
initialLeaseWatchState,
|
|
65
|
+
LEASE_MIN_DARK_WINDOW_MS,
|
|
66
|
+
LEASE_POLL_INTERVAL_MS,
|
|
67
|
+
type LeaseChannelState,
|
|
68
|
+
type LeasePollResult,
|
|
69
|
+
type LeaseVoidReason,
|
|
70
|
+
type LeaseWatchState,
|
|
71
|
+
reduceLeasePoll,
|
|
72
|
+
} from '../editor-lease';
|
|
73
|
+
import { publishEditorLeaseView } from '../editor-lease-view';
|
|
74
|
+
import { dismissNotification, notify } from '../editor-notifications';
|
|
75
|
+
import { connectEvents, readControlChannelState } from '../editor-presence';
|
|
76
|
+
import { clearSessionOrphanRecord, writeSessionOrphanRecord } from '../session-orphan-record';
|
|
77
|
+
import { clearSessionEnded, markSessionEnded } from '../session-tombstone';
|
|
78
|
+
|
|
79
|
+
/** The last path segment of a project root, for user-facing copy. */
|
|
80
|
+
function projectName(path: string | null): string | null {
|
|
81
|
+
if (!path) return null;
|
|
82
|
+
const segments = path.split(/[\\/]/).filter(Boolean);
|
|
83
|
+
return segments.at(-1) ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The one-shot teardown a void lease triggers: drop the Colyseus room (via the
|
|
88
|
+
* game's React unmount) and stop the sim. `exitPlayMode` is imported lazily so
|
|
89
|
+
* the guard — and therefore AppRoot's module graph — never eagerly pulls in the
|
|
90
|
+
* engine renderer play-mode drags along; it is only needed on the rare void,
|
|
91
|
+
* not on the poll cadence. A teardown throw is swallowed: the overlay must
|
|
92
|
+
* still tell the user the tab is dead.
|
|
93
|
+
*/
|
|
94
|
+
async function tearDownVoidLease(): Promise<void> {
|
|
95
|
+
try {
|
|
96
|
+
const { stopAllLiveSessions } = await import('../live-session-registry');
|
|
97
|
+
stopAllLiveSessions();
|
|
98
|
+
} catch {
|
|
99
|
+
/* the overlay is raised regardless — see above */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* What the guard is currently showing. `void` carries whether a fresh boot is
|
|
105
|
+
* on offer, so the overlay can hand back a way out instead of being a dead end.
|
|
106
|
+
*/
|
|
107
|
+
type GuardView =
|
|
108
|
+
| { kind: 'quiet' }
|
|
109
|
+
/** `busy` — polls failing over a live channel. `stale` — a real void the
|
|
110
|
+
* user chose to keep reading; the page is a snapshot and says so. */
|
|
111
|
+
| { kind: 'degraded'; subject: 'busy' | 'stale' }
|
|
112
|
+
| { kind: 'void'; reason: LeaseVoidReason; reloadOffered: boolean };
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Mounts the watchdog and renders its notice. Place it at the editor root
|
|
116
|
+
* (AppRoot) so the overlay covers the whole editor.
|
|
117
|
+
*/
|
|
118
|
+
export function EditorLeaseGuard() {
|
|
119
|
+
const [view, setView] = useState<GuardView>({ kind: 'quiet' });
|
|
120
|
+
// The rolling watch state and the one-shot latches live in refs: the interval
|
|
121
|
+
// closure reads them without re-subscribing, and the teardown must run
|
|
122
|
+
// exactly once per void.
|
|
123
|
+
const stateRef = useRef<LeaseWatchState>(initialLeaseWatchState(Date.now()));
|
|
124
|
+
const voidedRef = useRef<LeaseVoidReason | null>(null);
|
|
125
|
+
// Sticky for the page: once the user says "continue anyway", this guard never
|
|
126
|
+
// blocks them again. A second scrim after an explicit dismissal is the same
|
|
127
|
+
// trap wearing a later timestamp.
|
|
128
|
+
const dismissedRef = useRef(false);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
let cancelled = false;
|
|
132
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
133
|
+
|
|
134
|
+
const stop = () => {
|
|
135
|
+
if (timer !== null) {
|
|
136
|
+
clearInterval(timer);
|
|
137
|
+
timer = null;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Say it, THEN clean up. The timer keeps running for `server-gone` so the
|
|
142
|
+
// tombstone can recover; `taken-over` stops, because that tab yielded to a
|
|
143
|
+
// live window and must never come back to fight it.
|
|
144
|
+
const enterVoidState = async (reason: LeaseVoidReason) => {
|
|
145
|
+
voidedRef.current = reason;
|
|
146
|
+
// The page-wide latch: from here the command listener refuses relayed
|
|
147
|
+
// traffic and reports itself detached, so the server's tab table stops
|
|
148
|
+
// treating this page as a live editor (`session-tombstone.ts`). The
|
|
149
|
+
// overlay below is the human half of the same fact.
|
|
150
|
+
markSessionEnded(reason);
|
|
151
|
+
if (reason === 'taken-over') stop();
|
|
152
|
+
// ORDER IS THE POINT. Everything that tells somebody — the overlay on
|
|
153
|
+
// screen and the note for the next session — happens before the
|
|
154
|
+
// teardown, and neither touches the network. The teardown is a dynamic
|
|
155
|
+
// import; today it resolves from the module cache, but the server whose
|
|
156
|
+
// module graph it belongs to is by definition dead at this instant, and
|
|
157
|
+
// a page that announces its own death only after a fetch to the corpse
|
|
158
|
+
// has the failure mode this whole file exists to remove.
|
|
159
|
+
if (reason === 'server-gone') {
|
|
160
|
+
writeSessionOrphanRecord({
|
|
161
|
+
at: Date.now(),
|
|
162
|
+
reason: 'server-gone',
|
|
163
|
+
project: stateRef.current.bootIdentity?.project ?? null,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
setView(
|
|
167
|
+
dismissedRef.current
|
|
168
|
+
? { kind: 'degraded', subject: 'stale' }
|
|
169
|
+
: { kind: 'void', reason, reloadOffered: false },
|
|
170
|
+
);
|
|
171
|
+
await tearDownVoidLease();
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// The tombstone watch: this page has already declared itself dead and is
|
|
175
|
+
// now only asking whether anything came back. `taken-over` never reaches
|
|
176
|
+
// here — its timer stops, because that tab yielded to a live window.
|
|
177
|
+
const tickTombstone = (result: LeasePollResult, channel: LeaseChannelState, now: number) => {
|
|
178
|
+
switch (decideLeaseRecovery(stateRef.current.bootIdentity, result)) {
|
|
179
|
+
case 'resume': {
|
|
180
|
+
// The SAME process answered: it was never gone, so nothing here is
|
|
181
|
+
// stale and the session is kept. This is the branch that turns a
|
|
182
|
+
// false void back into a working editor.
|
|
183
|
+
voidedRef.current = null;
|
|
184
|
+
// …and the page is live again, so lift the tombstone: control
|
|
185
|
+
// traffic must stop being refused the moment the evidence says the
|
|
186
|
+
// process never died.
|
|
187
|
+
clearSessionEnded();
|
|
188
|
+
// Retract the note too. It said this window was orphaned; the
|
|
189
|
+
// process answering proves it was not, and a record nobody can
|
|
190
|
+
// trust is worse than none.
|
|
191
|
+
clearSessionOrphanRecord();
|
|
192
|
+
stateRef.current = reduceLeasePoll(stateRef.current, result, channel, now);
|
|
193
|
+
setView({ kind: 'quiet' });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
case 'reload':
|
|
197
|
+
// A new process serves our project: the page IS stale, so a full
|
|
198
|
+
// boot is the honest recovery (../editor-lease.ts revival contract).
|
|
199
|
+
// Unless the user asked to keep this window — "continue anyway"
|
|
200
|
+
// means exactly "do not take it away from me", and overriding that
|
|
201
|
+
// is the same class of defect as the scrim it dismissed.
|
|
202
|
+
if (dismissedRef.current) return;
|
|
203
|
+
window.location.reload();
|
|
204
|
+
return;
|
|
205
|
+
case 'offer-reload':
|
|
206
|
+
setView((current) =>
|
|
207
|
+
current.kind === 'void' && !current.reloadOffered
|
|
208
|
+
? { ...current, reloadOffered: true }
|
|
209
|
+
: current,
|
|
210
|
+
);
|
|
211
|
+
return;
|
|
212
|
+
default:
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const tick = async () => {
|
|
218
|
+
const result = await pollEditorLeaseIdentity();
|
|
219
|
+
const channel = readControlChannelState();
|
|
220
|
+
const now = Date.now();
|
|
221
|
+
if (cancelled) return;
|
|
222
|
+
|
|
223
|
+
if (voidedRef.current !== null) {
|
|
224
|
+
tickTombstone(result, channel, now);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
stateRef.current = reduceLeasePoll(stateRef.current, result, channel, now);
|
|
229
|
+
const next = decideLeaseState({
|
|
230
|
+
consecutiveFailures: stateRef.current.consecutiveFailures,
|
|
231
|
+
unbackedFailures: stateRef.current.unbackedFailures,
|
|
232
|
+
failureThreshold: DEFAULT_LEASE_FAILURE_THRESHOLD,
|
|
233
|
+
darkForMs: now - stateRef.current.darkSince,
|
|
234
|
+
minDarkWindowMs: LEASE_MIN_DARK_WINDOW_MS,
|
|
235
|
+
bootIdentity: stateRef.current.bootIdentity,
|
|
236
|
+
lastIdentity: stateRef.current.lastIdentity,
|
|
237
|
+
});
|
|
238
|
+
if (next.status === 'void' && next.reason !== null) {
|
|
239
|
+
await enterVoidState(next.reason);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
setView(
|
|
243
|
+
next.status === 'degraded' ? { kind: 'degraded', subject: 'busy' } : { kind: 'quiet' },
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// One poll at a time. The interval and the event triggers below all land
|
|
248
|
+
// in here, and overlapping polls would fold two readings of the same
|
|
249
|
+
// moment into the counter as if they were two moments.
|
|
250
|
+
let polling = false;
|
|
251
|
+
const poll = () => {
|
|
252
|
+
if (polling || cancelled) return;
|
|
253
|
+
polling = true;
|
|
254
|
+
void tick().finally(() => {
|
|
255
|
+
polling = false;
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// The page was NOT RUNNING and now is. Measured 2026-08-19: a hidden tab
|
|
260
|
+
// whose server had been killed made no network attempt of any kind for
|
|
261
|
+
// four and a half minutes — the browser had suspended the page and its
|
|
262
|
+
// heartbeat worker with it. Until the tab is shown again there is no
|
|
263
|
+
// watchdog, only a timer the browser has stopped calling; the ONLY moment
|
|
264
|
+
// that matters is this one, and waiting a further interval to look would
|
|
265
|
+
// spend the user's first clicks hanging on a server the page could have
|
|
266
|
+
// asked about immediately.
|
|
267
|
+
//
|
|
268
|
+
// The failure run is dropped first: those counters are testimony about
|
|
269
|
+
// elapsed time and this page slept through it (see `forgetLeaseFailures`).
|
|
270
|
+
const wake = () => {
|
|
271
|
+
if (cancelled) return;
|
|
272
|
+
if (voidedRef.current === null) {
|
|
273
|
+
stateRef.current = forgetLeaseFailures(stateRef.current, Date.now());
|
|
274
|
+
}
|
|
275
|
+
poll();
|
|
276
|
+
};
|
|
277
|
+
const onVisibility = () => {
|
|
278
|
+
if (document.visibilityState === 'visible') wake();
|
|
279
|
+
};
|
|
280
|
+
document.addEventListener('visibilitychange', onVisibility);
|
|
281
|
+
window.addEventListener('pageshow', wake);
|
|
282
|
+
|
|
283
|
+
// The control socket is the fast half of the evidence: a killed process
|
|
284
|
+
// drops it in milliseconds, while the poll cadence is seconds. Looking the
|
|
285
|
+
// instant it moves — in EITHER direction — is what makes the notice track
|
|
286
|
+
// the server rather than the clock. No reset here: unlike a wake, these
|
|
287
|
+
// fire on a page that has been awake and watching, so its readings stand,
|
|
288
|
+
// and resetting on the reconnect attempts a dead server produces would
|
|
289
|
+
// stop the run from ever accumulating.
|
|
290
|
+
let channelEvents: { close(): void } | null = null;
|
|
291
|
+
try {
|
|
292
|
+
const events = connectEvents();
|
|
293
|
+
events.addEventListener('error', poll);
|
|
294
|
+
events.addEventListener('open', poll);
|
|
295
|
+
channelEvents = events;
|
|
296
|
+
} catch {
|
|
297
|
+
// No control channel available on this page (no `EventSource`, a
|
|
298
|
+
// transport the bootstrap never built). The interval below still runs;
|
|
299
|
+
// the guard is simply back to its slower reading.
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
timer = setInterval(poll, LEASE_POLL_INTERVAL_MS);
|
|
303
|
+
return () => {
|
|
304
|
+
cancelled = true;
|
|
305
|
+
stop();
|
|
306
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
307
|
+
window.removeEventListener('pageshow', wake);
|
|
308
|
+
channelEvents?.close();
|
|
309
|
+
};
|
|
310
|
+
}, []);
|
|
311
|
+
|
|
312
|
+
// STATE goes to the status bar (`editor-lease-view.ts`); the EVENT goes to
|
|
313
|
+
// the notification stack. Neither blocks the page: the modal this guard
|
|
314
|
+
// used to raise ate every click on a window that was often perfectly
|
|
315
|
+
// readable, and the owner named it (2026-09-04) as the wrong shape.
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
publishEditorLeaseView(view);
|
|
318
|
+
const name = projectName(stateRef.current.bootIdentity?.project ?? null);
|
|
319
|
+
const dismiss = () => {
|
|
320
|
+
dismissedRef.current = true;
|
|
321
|
+
setView({ kind: 'degraded', subject: 'stale' });
|
|
322
|
+
};
|
|
323
|
+
if (view.kind === 'quiet') {
|
|
324
|
+
dismissNotification(LEASE_NOTIFICATION_ID);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (view.kind === 'degraded') {
|
|
328
|
+
notify({
|
|
329
|
+
id: LEASE_NOTIFICATION_ID,
|
|
330
|
+
tone: 'warning',
|
|
331
|
+
sticky: true,
|
|
332
|
+
...(view.subject === 'busy'
|
|
333
|
+
? {
|
|
334
|
+
title: 'Editor server is slow to answer.',
|
|
335
|
+
detail:
|
|
336
|
+
'Still connected — this window keeps working, and this clears itself when the server catches up.',
|
|
337
|
+
}
|
|
338
|
+
: {
|
|
339
|
+
title: "This window's editor server has stopped.",
|
|
340
|
+
detail:
|
|
341
|
+
'You chose to keep reading it — nothing here saves, and it will not reconnect on its own. Reload once the editor is served here again.',
|
|
342
|
+
actions: [{ label: 'Reload', primary: true, run: () => window.location.reload() }],
|
|
343
|
+
}),
|
|
344
|
+
});
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
notify({
|
|
348
|
+
id: LEASE_NOTIFICATION_ID,
|
|
349
|
+
tone: 'error',
|
|
350
|
+
sticky: true,
|
|
351
|
+
title: 'Editor disconnected.',
|
|
352
|
+
detail:
|
|
353
|
+
view.reason === 'taken-over'
|
|
354
|
+
? `${name ? `${name}'s` : "This project's"} editor is now running in another window. This one has yielded.`
|
|
355
|
+
: `This window's editor server has stopped — nothing here is saved, and a running game has left its room. Reopen with \`volter-editor edit ${name ?? '<project>'}\`${name ? `, or leave this window open: it reloads by itself when ${name}'s editor is served here again.` : '.'}`,
|
|
356
|
+
onDismiss: () => {
|
|
357
|
+
if (!dismissedRef.current) dismiss();
|
|
358
|
+
},
|
|
359
|
+
actions: [
|
|
360
|
+
...(view.reloadOffered
|
|
361
|
+
? [{ label: 'Reload', primary: true, run: () => window.location.reload() }]
|
|
362
|
+
: []),
|
|
363
|
+
{ label: 'Close window', run: () => window.close() },
|
|
364
|
+
{ label: 'Continue anyway', run: dismiss },
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
}, [view]);
|
|
368
|
+
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const LEASE_NOTIFICATION_ID = 'editor-lease';
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one error card the pre-editor surfaces render.
|
|
3
|
+
*
|
|
4
|
+
* Two moods, one component:
|
|
5
|
+
* - `inline` — an action failure (create/open/browse) above a surface the user
|
|
6
|
+
* is still using.
|
|
7
|
+
* - `startup` — the BOOT failure, where the card is the page: the title is
|
|
8
|
+
* promoted to the page's only `<h1>` and the recovery command is labelled
|
|
9
|
+
* (`StartupErrorScreen`).
|
|
10
|
+
*
|
|
11
|
+
* It lives in its own module so the boot-failure surface does not have to
|
|
12
|
+
* import the launcher to reuse the card — the whole point of that surface is
|
|
13
|
+
* that the launcher is NOT on screen (PD-6/7 residual B: an unreadable
|
|
14
|
+
* manifest rendered "What do you want to do?" with a banner over it, so a
|
|
15
|
+
* loud server-side throw still looked like a fresh launcher in a screenshot).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { faCheck, faCopy, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
|
19
|
+
import type { StartupRecovery } from '@volter/editor-sdk/session/editor-compatibility';
|
|
20
|
+
import { Button, EditorIcon } from '@volter/editor-sdk/widgets';
|
|
21
|
+
import { useEffect, useState } from 'react';
|
|
22
|
+
|
|
23
|
+
export interface ScreenError {
|
|
24
|
+
message: string;
|
|
25
|
+
recovery?: StartupRecovery;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ErrorBanner({
|
|
29
|
+
error,
|
|
30
|
+
onDismiss,
|
|
31
|
+
onRetry,
|
|
32
|
+
startup = false,
|
|
33
|
+
}: {
|
|
34
|
+
error: ScreenError;
|
|
35
|
+
onDismiss?: (() => void) | undefined;
|
|
36
|
+
onRetry?: (() => void) | undefined;
|
|
37
|
+
startup?: boolean;
|
|
38
|
+
}) {
|
|
39
|
+
const command = error.recovery && 'command' in error.recovery ? error.recovery.command : null;
|
|
40
|
+
const [copied, setCopied] = useState(false);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!copied) return;
|
|
44
|
+
const timer = window.setTimeout(() => setCopied(false), 1600);
|
|
45
|
+
return () => window.clearTimeout(timer);
|
|
46
|
+
}, [copied]);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div role="alert" className="vgai-error-card vgai-shell-rise">
|
|
50
|
+
<div className="vgai-error-card-icon" aria-hidden="true">
|
|
51
|
+
<EditorIcon icon={faTriangleExclamation} />
|
|
52
|
+
</div>
|
|
53
|
+
<div className="vgai-error-card-body">
|
|
54
|
+
{startup ? (
|
|
55
|
+
<h1 className="vgai-error-card-title">
|
|
56
|
+
{error.recovery?.title ?? 'Couldn’t open this project'}
|
|
57
|
+
</h1>
|
|
58
|
+
) : (
|
|
59
|
+
error.recovery && <div className="vgai-error-card-title">{error.recovery.title}</div>
|
|
60
|
+
)}
|
|
61
|
+
{error.recovery && <p className="vgai-error-card-guidance">{error.recovery.guidance}</p>}
|
|
62
|
+
<div
|
|
63
|
+
className="vgai-error-card-message"
|
|
64
|
+
data-testid={startup ? 'project-startup-error-details' : undefined}
|
|
65
|
+
>
|
|
66
|
+
{error.message}
|
|
67
|
+
</div>
|
|
68
|
+
{command && (
|
|
69
|
+
<div>
|
|
70
|
+
{startup && (
|
|
71
|
+
<div className="vgai-error-card-recovery-label">RUN FROM THE PROJECT FOLDER</div>
|
|
72
|
+
)}
|
|
73
|
+
<div className="vgai-error-card-command">
|
|
74
|
+
<code
|
|
75
|
+
data-testid={
|
|
76
|
+
startup ? 'project-startup-recovery-command' : 'project-screen-recovery-command'
|
|
77
|
+
}
|
|
78
|
+
>
|
|
79
|
+
{command}
|
|
80
|
+
</code>
|
|
81
|
+
<Button
|
|
82
|
+
type="button"
|
|
83
|
+
variant="secondary"
|
|
84
|
+
size="compact"
|
|
85
|
+
onClick={() => {
|
|
86
|
+
void navigator.clipboard?.writeText(command);
|
|
87
|
+
setCopied(true);
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<EditorIcon icon={copied ? faCheck : faCopy} aria-hidden="true" />
|
|
91
|
+
{copied ? 'Copied' : 'Copy'}
|
|
92
|
+
</Button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
<div className="vgai-error-card-actions">
|
|
97
|
+
{onRetry && (
|
|
98
|
+
<Button
|
|
99
|
+
type="button"
|
|
100
|
+
variant="primary"
|
|
101
|
+
className="vgai-shell-cta"
|
|
102
|
+
data-testid="retry-project-detection"
|
|
103
|
+
onClick={onRetry}
|
|
104
|
+
>
|
|
105
|
+
Retry
|
|
106
|
+
</Button>
|
|
107
|
+
)}
|
|
108
|
+
{onDismiss && (
|
|
109
|
+
<Button
|
|
110
|
+
type="button"
|
|
111
|
+
variant="ghost"
|
|
112
|
+
className="vgai-shell-cta"
|
|
113
|
+
aria-label="Dismiss error"
|
|
114
|
+
onClick={onDismiss}
|
|
115
|
+
>
|
|
116
|
+
Dismiss
|
|
117
|
+
</Button>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composite folder thumbnail for the asset-browser grid. Resolves the
|
|
3
|
+
* folder's representative contents through the memoized `getFolderPreview`
|
|
4
|
+
* scan and composites up to four cells inside the standard tile preview box:
|
|
5
|
+
* 1 → full bleed, 2 → side-by-side halves, 3 → hero left + two stacked right,
|
|
6
|
+
* 4 → 2×2 grid. Cells reuse the exact asset-tile pipelines — `<img>` for
|
|
7
|
+
* images, manifest-then-queued offscreen render for models (at LOWER queue
|
|
8
|
+
* priority than direct asset tiles, so folder cells never delay them), and
|
|
9
|
+
* the synthetic glyphs for the remaining kinds. The flat folder icon shows
|
|
10
|
+
* until the summary resolves (then the composite fades in, no layout shift);
|
|
11
|
+
* an empty folder renders a distinct dimmed open-folder state.
|
|
12
|
+
*
|
|
13
|
+
* The whole composite is decorative (`aria-hidden`): the folder card itself
|
|
14
|
+
* is the `role="option"` whose accessible name is the folder name, and any
|
|
15
|
+
* label inside the preview would concatenate into that name — breaking
|
|
16
|
+
* exact-name selectors and double-announcing to screen readers.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { faFolder, faFolderOpen } from '@fortawesome/free-solid-svg-icons';
|
|
20
|
+
import { EditorIcon } from '@volter/editor-sdk/widgets';
|
|
21
|
+
import { useEffect, useState } from 'react';
|
|
22
|
+
import { assetCapabilities } from '../asset-workflow/asset-capabilities';
|
|
23
|
+
import {
|
|
24
|
+
type FolderPreviewItem,
|
|
25
|
+
type FolderPreviewSummary,
|
|
26
|
+
getFolderPreview,
|
|
27
|
+
} from '../asset-workflow/folder-preview';
|
|
28
|
+
import { type AssetRootId, assetRootServingUrl } from '../asset-workflow/project-asset-roots';
|
|
29
|
+
import {
|
|
30
|
+
AssetIcon,
|
|
31
|
+
FOLDER_CELL_THUMBNAIL_PRIORITY,
|
|
32
|
+
TypedAssetThumbnail,
|
|
33
|
+
useModelThumbnailSource,
|
|
34
|
+
} from './asset-thumbnails';
|
|
35
|
+
|
|
36
|
+
/** Root-relative preview item path → the URL its root serves it at. */
|
|
37
|
+
function folderItemUrl(root: AssetRootId, item: FolderPreviewItem): string {
|
|
38
|
+
return assetRootServingUrl(root, item.path);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function FolderPreviewImageCell({ root, item }: { root: AssetRootId; item: FolderPreviewItem }) {
|
|
42
|
+
const [failed, setFailed] = useState(false);
|
|
43
|
+
if (failed) return <AssetIcon kind="image" size={14} />;
|
|
44
|
+
return (
|
|
45
|
+
<img
|
|
46
|
+
src={folderItemUrl(root, item)}
|
|
47
|
+
alt={item.name}
|
|
48
|
+
loading="lazy"
|
|
49
|
+
onError={() => setFailed(true)}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function FolderPreviewModelCell({ root, item }: { root: AssetRootId; item: FolderPreviewItem }) {
|
|
55
|
+
const thumbnail = useModelThumbnailSource(
|
|
56
|
+
folderItemUrl(root, item),
|
|
57
|
+
FOLDER_CELL_THUMBNAIL_PRIORITY,
|
|
58
|
+
);
|
|
59
|
+
if (thumbnail.failed) return <AssetIcon kind="model" size={14} />;
|
|
60
|
+
// Quiet while resolving: the inset cell background reads as pending —
|
|
61
|
+
// folder cells never show per-cell spinners.
|
|
62
|
+
if (!thumbnail.src) return null;
|
|
63
|
+
return (
|
|
64
|
+
<img
|
|
65
|
+
src={thumbnail.src}
|
|
66
|
+
alt={item.name}
|
|
67
|
+
data-fit="contain"
|
|
68
|
+
onError={thumbnail.fromManifest ? thumbnail.rejectManifest : undefined}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function FolderPreviewCell({ root, item }: { root: AssetRootId; item: FolderPreviewItem }) {
|
|
74
|
+
if (assetCapabilities(item.name).editor === 'environment') {
|
|
75
|
+
return <TypedAssetThumbnail kind="image" name={item.name} variant="cell" />;
|
|
76
|
+
}
|
|
77
|
+
if (item.kind === 'image') return <FolderPreviewImageCell root={root} item={item} />;
|
|
78
|
+
if (item.kind === 'model') return <FolderPreviewModelCell root={root} item={item} />;
|
|
79
|
+
return <TypedAssetThumbnail kind={item.kind} name={item.name} variant="cell" />;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface FolderPreviewTileProps {
|
|
83
|
+
/** Which asset root this folder lives in — passed through to
|
|
84
|
+
* `getFolderPreview` AND used to build each cell's serving URL. */
|
|
85
|
+
root: AssetRootId;
|
|
86
|
+
/** Folder path relative to the asset root. */
|
|
87
|
+
folderPath: string;
|
|
88
|
+
/** Bump after `invalidateFolderPreviews` to re-resolve without remounting. */
|
|
89
|
+
revision: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function FolderPreviewTile({ root, folderPath, revision }: FolderPreviewTileProps) {
|
|
93
|
+
const [summary, setSummary] = useState<FolderPreviewSummary | 'failed' | null>(null);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
setSummary(null);
|
|
97
|
+
}, [root, folderPath]);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
let cancelled = false;
|
|
101
|
+
getFolderPreview(root, folderPath).then(
|
|
102
|
+
(result) => {
|
|
103
|
+
if (!cancelled) setSummary(result);
|
|
104
|
+
},
|
|
105
|
+
() => {
|
|
106
|
+
if (!cancelled) setSummary('failed');
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
return () => {
|
|
110
|
+
cancelled = true;
|
|
111
|
+
};
|
|
112
|
+
}, [root, folderPath, revision]);
|
|
113
|
+
|
|
114
|
+
// Loading (and scan failure): the classic flat folder icon, no spinner —
|
|
115
|
+
// the composite swaps in with a fade once the summary resolves.
|
|
116
|
+
if (summary === null || summary === 'failed') return <AssetIcon kind="folder" />;
|
|
117
|
+
|
|
118
|
+
if (summary.totalAssets === 0) {
|
|
119
|
+
return (
|
|
120
|
+
<div className="vgai-folder-preview" data-testid="folder-preview-empty" aria-hidden="true">
|
|
121
|
+
<div className="vgai-folder-preview__empty">
|
|
122
|
+
<EditorIcon icon={faFolderOpen} className="vgai-folder-preview__empty-glyph" />
|
|
123
|
+
<span className="vgai-folder-preview__empty-caption">Empty</span>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const count = summary.scannedAll ? `${summary.totalAssets}` : `${summary.totalAssets}+`;
|
|
130
|
+
|
|
131
|
+
// Assets exist but none are previewable — keep the flat folder identity,
|
|
132
|
+
// still faded in and counted so the tile reflects the resolved summary.
|
|
133
|
+
if (summary.items.length === 0) {
|
|
134
|
+
return (
|
|
135
|
+
<div className="vgai-folder-preview" data-testid="folder-preview-plain" aria-hidden="true">
|
|
136
|
+
<div className="vgai-folder-preview__fallback">
|
|
137
|
+
<AssetIcon kind="folder" />
|
|
138
|
+
</div>
|
|
139
|
+
<span className="vgai-folder-preview__count">{count}</span>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div className="vgai-folder-preview" data-testid="folder-preview" aria-hidden="true">
|
|
146
|
+
<div className="vgai-folder-preview__grid" data-count={summary.items.length}>
|
|
147
|
+
{summary.items.map((item) => (
|
|
148
|
+
<div key={item.path} className="vgai-folder-preview__cell">
|
|
149
|
+
<FolderPreviewCell root={root} item={item} />
|
|
150
|
+
</div>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
<span className="vgai-folder-preview__chip">
|
|
154
|
+
<EditorIcon icon={faFolder} />
|
|
155
|
+
</span>
|
|
156
|
+
<span className="vgai-folder-preview__count">{count}</span>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|