@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,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE SCENE TABLE'S SECOND CLIENT — `open()` against the RUNNING game.
|
|
3
|
+
*
|
|
4
|
+
* ARCHITECTURE-CORE §Editor: "the scene table … is ONE adapter declaration with
|
|
5
|
+
* two clients — the Edit tab row projects it as isolation documents … and
|
|
6
|
+
* Play's `open()` walks it as live navigation." `scene-document-plan.ts` +
|
|
7
|
+
* `components/scene-documents.tsx` are the first client; this module is the
|
|
8
|
+
* second, and both are reached through the ONE `open` verb in
|
|
9
|
+
* `command-listener.ts`, which asks this one first whenever a game is live.
|
|
10
|
+
*
|
|
11
|
+
* ZERO INFERENCE, twice over:
|
|
12
|
+
*
|
|
13
|
+
* 1. WHICH contract id an entry is, is the adapter's own declaration
|
|
14
|
+
* (`reach: { kind: 'game-contract', sceneId }`). Nothing here matches
|
|
15
|
+
* entry ids against contract ids, or assumes the two vocabularies coincide.
|
|
16
|
+
* 2. WHETHER that id is navigable is the GAME's answer, at the moment of use:
|
|
17
|
+
* the switch goes through the bound scenes projection's own
|
|
18
|
+
* `goToScene` (`authoring/contract-scenes-stories.ts`), which validates
|
|
19
|
+
* membership against the game's own `list()` and carries the mount's
|
|
20
|
+
* held-repaint seam. Calling the contract's `goTo` directly would route
|
|
21
|
+
* around both.
|
|
22
|
+
*
|
|
23
|
+
* Every seam is injected ({@link LiveSceneSession}) so the whole matrix is
|
|
24
|
+
* decidable headlessly — no mounted game, no workspace, no browser.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { LiveSceneTable } from '@volter/editor-sdk/host';
|
|
28
|
+
import type { DocumentEntry } from '@volter/editor-project/adapter/adapter-module';
|
|
29
|
+
|
|
30
|
+
/** What a live game session offers this verb. */
|
|
31
|
+
export interface LiveSceneSession {
|
|
32
|
+
/**
|
|
33
|
+
* The running game's BOUND scenes projection, or `null` when the game
|
|
34
|
+
* publishes none.
|
|
35
|
+
*
|
|
36
|
+
* "Bound" is load-bearing: this is the object the mount installed on the
|
|
37
|
+
* game's own authoring adapter — the same one the inspector's scene picker
|
|
38
|
+
* drives — so a switch made here is the same switch a click makes, held
|
|
39
|
+
* repaint and all.
|
|
40
|
+
*/
|
|
41
|
+
readonly scenes: () => LiveSceneTable | null;
|
|
42
|
+
/**
|
|
43
|
+
* Activate the document the running game is presented in; `false` when the
|
|
44
|
+
* session has none open. An ingested game's mount IS the running game, and
|
|
45
|
+
* it draws into the Game document — there is no second document a
|
|
46
|
+
* `root-mount` scene could name.
|
|
47
|
+
*/
|
|
48
|
+
readonly activateGameDocument: () => boolean;
|
|
49
|
+
/** The Game document's id, for the answer. */
|
|
50
|
+
readonly gameDocumentId: string;
|
|
51
|
+
/**
|
|
52
|
+
* Host remount of a native swap-slot scene: stop this play run and start a
|
|
53
|
+
* new one with the entrypoint's selection const rewritten to `key`. Absent
|
|
54
|
+
* when this session has no serve-time rewrite (an ingest root), which is
|
|
55
|
+
* when `entrypoint-selection` still refuses `SCENE_NOT_OPENABLE_LIVE`.
|
|
56
|
+
*/
|
|
57
|
+
readonly remountSelection?: (args: {
|
|
58
|
+
readonly selection: string;
|
|
59
|
+
readonly key: string;
|
|
60
|
+
readonly regionId: string;
|
|
61
|
+
}) => Promise<{ readonly ok: true } | { readonly ok: false; readonly error: string }>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A coded refusal from the live half — the SDK matches `code`, never prose. */
|
|
65
|
+
export interface LiveSceneOpenRefusal {
|
|
66
|
+
readonly ok: false;
|
|
67
|
+
readonly code:
|
|
68
|
+
| 'SCENE_CONTRACT_UNAVAILABLE'
|
|
69
|
+
| 'SCENE_NOT_IN_CONTRACT'
|
|
70
|
+
| 'SCENE_SWITCH_FAILED'
|
|
71
|
+
| 'SCENE_NOT_OPENABLE_LIVE'
|
|
72
|
+
| 'SCENE_DOCUMENT_NOT_MOUNTED';
|
|
73
|
+
readonly error: string;
|
|
74
|
+
/** The ids the game itself published, when the refusal is about membership. */
|
|
75
|
+
readonly known?: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type LiveSceneOpenResult =
|
|
79
|
+
| {
|
|
80
|
+
readonly ok: true;
|
|
81
|
+
readonly documentId: string;
|
|
82
|
+
readonly title: string;
|
|
83
|
+
/** The game's own answer: what was asked for, and where it ended up. */
|
|
84
|
+
readonly scene?: { readonly requested: string; readonly current: string | null };
|
|
85
|
+
/**
|
|
86
|
+
* Present when this open restarted play rather than navigating a live
|
|
87
|
+
* contract — the swap slot is a module-level const, so the honest
|
|
88
|
+
* live path is remount-at-key.
|
|
89
|
+
*/
|
|
90
|
+
readonly restart?: true;
|
|
91
|
+
}
|
|
92
|
+
| LiveSceneOpenRefusal;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Open ONE scene-table entry against the running game, or answer `null` when
|
|
96
|
+
* this entry is not the live half's business — a `story` reach has a portable
|
|
97
|
+
* mount, and a `none` reach is the adapter's own declared dead end. Both are
|
|
98
|
+
* the Edit half's, unchanged, and answering `null` is how this module says so
|
|
99
|
+
* rather than duplicating those refusals.
|
|
100
|
+
*/
|
|
101
|
+
export async function openLiveSceneEntry(
|
|
102
|
+
entry: DocumentEntry,
|
|
103
|
+
session: LiveSceneSession,
|
|
104
|
+
): Promise<LiveSceneOpenResult | null> {
|
|
105
|
+
switch (entry.reach.kind) {
|
|
106
|
+
case 'game-contract': {
|
|
107
|
+
const scenes = session.scenes();
|
|
108
|
+
if (!scenes) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
code: 'SCENE_CONTRACT_UNAVAILABLE',
|
|
112
|
+
error:
|
|
113
|
+
`open: "${entry.id}" is reached through the running game's own scenes contract, ` +
|
|
114
|
+
'and the game running in this session publishes none (`window.vgaiGame.scenes`).',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const switched = scenes.goToScene(entry.reach.sceneId);
|
|
118
|
+
if (!switched.ok) {
|
|
119
|
+
// The projection's own membership refusal, passed through: the game's
|
|
120
|
+
// `list()` is the authority on what it can be sent to, and a host
|
|
121
|
+
// paraphrase would be the host claiming to know the game's screens.
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
code: 'SCENE_NOT_IN_CONTRACT',
|
|
125
|
+
error: `open: "${entry.id}" — ${switched.error}`,
|
|
126
|
+
known: switched.known,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const settled = await switched.settled;
|
|
130
|
+
if (settled.error !== undefined) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
code: 'SCENE_SWITCH_FAILED',
|
|
134
|
+
error: `open: "${entry.id}" — the game's own navigation failed: ${settled.error}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// Activation is best-effort AFTER the switch: the navigation is the
|
|
138
|
+
// gesture, and a session with no Game document open (headless control,
|
|
139
|
+
// a workspace the user rearranged) still navigated the game.
|
|
140
|
+
session.activateGameDocument();
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
documentId: session.gameDocumentId,
|
|
144
|
+
title: entry.label,
|
|
145
|
+
scene: { requested: settled.requested, current: settled.current },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
case 'root-mount': {
|
|
149
|
+
// The running game IS this scene — mounting the region opened it — so the
|
|
150
|
+
// whole gesture is showing the document it draws into. REACHED, so the
|
|
151
|
+
// planner carries it as `liveReach: 'root-mount'` and the Edit half
|
|
152
|
+
// grades a no-game session `SCENE_NAVIGATION_NOT_RUNNING`: start the
|
|
153
|
+
// game and this returns ok. babylon-space-truckers' `splashScreen` is
|
|
154
|
+
// the shipped instance (`authorable: false`, so it owns no Edit
|
|
155
|
+
// document — pressing ▶ is the whole answer, not a defect in its
|
|
156
|
+
// declaration).
|
|
157
|
+
if (!session.activateGameDocument()) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
code: 'SCENE_DOCUMENT_NOT_MOUNTED',
|
|
161
|
+
error: `open: "${entry.id}" is the running game itself, and no game document is open in this session.`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return { ok: true, documentId: session.gameDocumentId, title: entry.label };
|
|
165
|
+
}
|
|
166
|
+
case 'entrypoint-selection': {
|
|
167
|
+
// The slot is a module-level CONST — the running module cannot change
|
|
168
|
+
// scenes. The honest live path is a host remount: stop this play run and
|
|
169
|
+
// start a new one with the entrypoint served at `key`. That is REAL
|
|
170
|
+
// navigation (the next mount executes that key), stated as a restart.
|
|
171
|
+
// A session with no remount seam (hosted bundle, ingest) still refuses
|
|
172
|
+
// coded — never an in-place fake.
|
|
173
|
+
const remount = session.remountSelection;
|
|
174
|
+
const regionId = entry.region;
|
|
175
|
+
if (!remount || regionId === null) {
|
|
176
|
+
return {
|
|
177
|
+
ok: false,
|
|
178
|
+
code: 'SCENE_NOT_OPENABLE_LIVE',
|
|
179
|
+
error:
|
|
180
|
+
`open: "${entry.id}" sits at the entrypoint's \`${entry.reach.selection}\` swap slot, ` +
|
|
181
|
+
'and this session has no live remount for a native swap-slot scene — a running game ' +
|
|
182
|
+
'cannot be sent there in place. Stop the game and open it as a document instead.',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const remounted = await remount({
|
|
186
|
+
selection: entry.reach.selection,
|
|
187
|
+
key: entry.reach.key,
|
|
188
|
+
regionId,
|
|
189
|
+
});
|
|
190
|
+
if (!remounted.ok) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
code: 'SCENE_SWITCH_FAILED',
|
|
194
|
+
error: `open: "${entry.id}" — remount at \`${entry.reach.selection}["${entry.reach.key}"]\` failed: ${remounted.error}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
session.activateGameDocument();
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
documentId: session.gameDocumentId,
|
|
201
|
+
title: entry.label,
|
|
202
|
+
scene: { requested: entry.reach.key, current: entry.reach.key },
|
|
203
|
+
restart: true,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
case 'story':
|
|
207
|
+
case 'none':
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why the Scene viewport draws a world's fog OUT.
|
|
5
|
+
*
|
|
6
|
+
* ## The measured defect
|
|
7
|
+
*
|
|
8
|
+
* Fog is calibrated in metres from THE GAME'S OWN CAMERA. The racing-game
|
|
9
|
+
* mount declares `<fog args={['white', 0, 500]}>`, which is exactly right at
|
|
10
|
+
* its chase camera 20 units behind the car and meaningless anywhere else: its
|
|
11
|
+
* world is ~800 units across, so the editor framing that shows the whole track
|
|
12
|
+
* sits ~700 units back, and every visible surface is past the fog's far plane.
|
|
13
|
+
* Measured on that mount (2026-08-15): the framed Scene view is a featureless
|
|
14
|
+
* white blob — the track, the canyon and the horizon are all the same white.
|
|
15
|
+
*
|
|
16
|
+
* The Scene camera is not the game's camera. It is placed by framing, by a
|
|
17
|
+
* seed, or by the reader's own orbit, at whatever distance the world needs —
|
|
18
|
+
* so a distance-calibrated effect authored for another camera is not a look,
|
|
19
|
+
* it is an erasure.
|
|
20
|
+
*
|
|
21
|
+
* ## Why a view policy and not fog-aware framing
|
|
22
|
+
*
|
|
23
|
+
* The alternative was to have the framing respect the fog range — pick a
|
|
24
|
+
* distance inside it. It cannot work, and the numbers say so rather than an
|
|
25
|
+
* argument: this world's content diagonal is ~1,100 units against a fog far
|
|
26
|
+
* plane of 500, so no distance both shows the track and stays readable. Any
|
|
27
|
+
* fog-aware framing would have to stop showing the world to keep the fog,
|
|
28
|
+
* which is backwards — the first look exists to show the world.
|
|
29
|
+
*
|
|
30
|
+
* So the fog goes, as a VIEW POLICY, in the same class as the shading modes:
|
|
31
|
+
* presentation-only, restored before anything else can observe it, and applied
|
|
32
|
+
* only to the editor's own draw. This is also the established answer in the
|
|
33
|
+
* genre — Unity's Scene view renders fog OFF by default and offers it as a
|
|
34
|
+
* scene-view effect toggle, for exactly this reason.
|
|
35
|
+
*
|
|
36
|
+
* The game's own look is not touched anywhere it belongs: the Game tab, play
|
|
37
|
+
* mode and every exported build render the game's camera through the game's
|
|
38
|
+
* fog. Only the authoring camera is exempted, and there is deliberately no
|
|
39
|
+
* toggle for it — a Scene view that can be washed white by a setting is a
|
|
40
|
+
* fault the reader has to diagnose, and nothing here is worth that.
|
|
41
|
+
*
|
|
42
|
+
* ## Why the parameters and not `scene.fog = null`
|
|
43
|
+
*
|
|
44
|
+
* `!!scene.fog` is part of three's shader-program cache key, so nulling it for
|
|
45
|
+
* one draw and restoring it for the next compiles and keeps a SECOND program
|
|
46
|
+
* variant for every material in the world. Neutralising the fog's own numbers
|
|
47
|
+
* leaves the program identical and the fog term at zero.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/** Far enough that no real depth reaches it; `near < far` keeps the linear
|
|
51
|
+
* fog's `smoothstep(near, far, depth)` well-defined. */
|
|
52
|
+
const NEUTRAL_FOG_NEAR = 1e20;
|
|
53
|
+
const NEUTRAL_FOG_FAR = 2e20;
|
|
54
|
+
|
|
55
|
+
type LinearFog = THREE.Fog & { isFog: true };
|
|
56
|
+
type ExpFog = THREE.FogExp2 & { isFogExp2: true };
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Run `draw` with `scene`'s fog contributing nothing, then put it back exactly
|
|
60
|
+
* as it was — including when `draw` throws.
|
|
61
|
+
*
|
|
62
|
+
* A scene with no fog costs one property read.
|
|
63
|
+
*/
|
|
64
|
+
export function withSceneFogNeutralized(scene: THREE.Scene, draw: () => void): void {
|
|
65
|
+
const fog = scene.fog;
|
|
66
|
+
if (!fog) {
|
|
67
|
+
draw();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const linear = (fog as LinearFog).isFog ? (fog as LinearFog) : null;
|
|
71
|
+
const exponential = (fog as ExpFog).isFogExp2 ? (fog as ExpFog) : null;
|
|
72
|
+
const near = linear?.near;
|
|
73
|
+
const far = linear?.far;
|
|
74
|
+
const density = exponential?.density;
|
|
75
|
+
if (linear) {
|
|
76
|
+
linear.near = NEUTRAL_FOG_NEAR;
|
|
77
|
+
linear.far = NEUTRAL_FOG_FAR;
|
|
78
|
+
}
|
|
79
|
+
if (exponential) exponential.density = 0;
|
|
80
|
+
try {
|
|
81
|
+
draw();
|
|
82
|
+
} finally {
|
|
83
|
+
if (linear && near !== undefined && far !== undefined) {
|
|
84
|
+
linear.near = near;
|
|
85
|
+
linear.far = far;
|
|
86
|
+
}
|
|
87
|
+
if (exponential && density !== undefined) exponential.density = density;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client half of SCOPED GAME CSS — fetch the open project's page-level
|
|
3
|
+
* stylesheet (already rewritten into `@scope ([data-vgai-game-styles])` by
|
|
4
|
+
* `server/scoped-game-css.ts`), install it ONCE, and report honestly when
|
|
5
|
+
* there is nothing to install.
|
|
6
|
+
*
|
|
7
|
+
* ONE `<style>` element per project, never one per surface. Every container
|
|
8
|
+
* that owns game DOM — each UI-board story card's content, each story
|
|
9
|
+
* document's preview, the ingest surface the game mounts into — becomes a
|
|
10
|
+
* scope root by wearing the attribute (`markGameCssScope`, `game-css-scope.ts`),
|
|
11
|
+
* and the single scoped sheet paints all of them. Duplicating the sheet per
|
|
12
|
+
* card would multiply a foreign game's whole stylesheet by the size of the
|
|
13
|
+
* board for no behavioural difference at all.
|
|
14
|
+
*
|
|
15
|
+
* ## The refusal path is the point
|
|
16
|
+
*
|
|
17
|
+
* A surface that shows a game's DOM with no page CSS looks BROKEN, not empty —
|
|
18
|
+
* the owner hit exactly that three times before this existed and read it as
|
|
19
|
+
* wreckage, because nothing on screen said what was missing. So this module's
|
|
20
|
+
* state is a value the surfaces read (`note`), not a silent side effect: a
|
|
21
|
+
* board card whose project declares no stylesheet, or whose stylesheet could
|
|
22
|
+
* not be read, or a browser with no `@scope`, says so on the card.
|
|
23
|
+
*
|
|
24
|
+
* `@scope` is the feature detection and it is deliberately a REFUSAL, not a
|
|
25
|
+
* fallback: without it the served sheet's `:scope` rules would not be
|
|
26
|
+
* contained, and installing it anyway would restyle the editor — the precise
|
|
27
|
+
* failure the whole feature exists to prevent.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { editorConsole } from './editor-console';
|
|
31
|
+
import { editorServerJson } from './editor-server-response';
|
|
32
|
+
|
|
33
|
+
/** What the open project's game CSS turned out to be. */
|
|
34
|
+
export interface ScopedGameStylesState {
|
|
35
|
+
/** True once the project's scoped stylesheet is attached to the document. */
|
|
36
|
+
readonly installed: boolean;
|
|
37
|
+
/** The declared stylesheet paths that were served, in emission order. */
|
|
38
|
+
readonly sources: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Surface-facing sentence naming what is missing, or `null` when the host
|
|
41
|
+
* has no evidence of a gap. Rendered ON a story card rather than logged,
|
|
42
|
+
* because the card is where the missing styling is SEEN.
|
|
43
|
+
*/
|
|
44
|
+
readonly note: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const NOTHING_DECLARED: ScopedGameStylesState = { installed: false, sources: [], note: null };
|
|
48
|
+
|
|
49
|
+
/** `data-*` marker on the one installed element, so a re-install can find it. */
|
|
50
|
+
const STYLE_ELEMENT_MARKER = 'data-vgai-scoped-game-css';
|
|
51
|
+
|
|
52
|
+
/** Per-project memo: the fetch+install runs once per open project, not per card. */
|
|
53
|
+
let pending: { projectRoot: string; state: Promise<ScopedGameStylesState> } | null = null;
|
|
54
|
+
|
|
55
|
+
/** Latest resolved state, for the synchronous readers (a board painting notes). */
|
|
56
|
+
let resolved: ScopedGameStylesState = NOTHING_DECLARED;
|
|
57
|
+
|
|
58
|
+
/** The installed sheet's own text — see {@link scopedGameStylesCssText}. */
|
|
59
|
+
let installedCss: string | null = null;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The installed stylesheet's text, or null when none is installed.
|
|
63
|
+
*
|
|
64
|
+
* Exists for ONE reader: the DOM capture leg (`composite-screenshot.ts`),
|
|
65
|
+
* which rasterizes a DETACHED clone of the surface through an SVG
|
|
66
|
+
* `foreignObject`. A detached fragment carries no document stylesheet, so
|
|
67
|
+
* without re-inlining this the look verb would photograph a correctly styled
|
|
68
|
+
* screen as unstyled — an instrument disagreeing with the product it is
|
|
69
|
+
* pointed at, which is worse than no instrument. (The same leg already
|
|
70
|
+
* re-inlines its motion freeze for the same structural reason.)
|
|
71
|
+
*/
|
|
72
|
+
export function scopedGameStylesCssText(): string | null {
|
|
73
|
+
return installedCss;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* True when this browser implements CSS `@scope`. Without it the served sheet
|
|
78
|
+
* cannot be contained, so nothing is installed at all.
|
|
79
|
+
*/
|
|
80
|
+
function supportsCssScope(): boolean {
|
|
81
|
+
return typeof (globalThis as { CSSScopeRule?: unknown }).CSSScopeRule === 'function';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fetch and install the open project's scoped game stylesheet, at most once per
|
|
86
|
+
* project. Safe to call from every surface that mounts game DOM — later calls
|
|
87
|
+
* await the same promise.
|
|
88
|
+
*/
|
|
89
|
+
export async function ensureScopedGameStyles(projectRoot: string): Promise<ScopedGameStylesState> {
|
|
90
|
+
if (pending?.projectRoot === projectRoot) return pending.state;
|
|
91
|
+
const state = loadScopedGameStyles(projectRoot);
|
|
92
|
+
pending = { projectRoot, state };
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The most recent resolved state. Surfaces that paint a note read this
|
|
98
|
+
* synchronously AFTER awaiting {@link ensureScopedGameStyles} — it exists so a
|
|
99
|
+
* board does not have to thread the promise through its own layout pass.
|
|
100
|
+
*/
|
|
101
|
+
export function scopedGameStylesState(): ScopedGameStylesState {
|
|
102
|
+
return resolved;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function loadScopedGameStyles(projectRoot: string): Promise<ScopedGameStylesState> {
|
|
106
|
+
const next = await fetchScopedGameStyles(projectRoot);
|
|
107
|
+
resolved = next;
|
|
108
|
+
return next;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function fetchScopedGameStyles(projectRoot: string): Promise<ScopedGameStylesState> {
|
|
112
|
+
let payload: { css?: unknown; sources?: unknown; note?: unknown };
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch('/__editor/scoped-game-css');
|
|
115
|
+
// A page fallback in front of the route fails the reader below and lands
|
|
116
|
+
// in the same graceful no-op story discovery takes. Nothing is claimed
|
|
117
|
+
// about the project's styling.
|
|
118
|
+
payload = await editorServerJson<typeof payload>(
|
|
119
|
+
response,
|
|
120
|
+
'Could not read the project scoped game CSS',
|
|
121
|
+
);
|
|
122
|
+
} catch {
|
|
123
|
+
return NOTHING_DECLARED;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const css = typeof payload.css === 'string' ? payload.css : '';
|
|
127
|
+
const sources = Array.isArray(payload.sources) ? (payload.sources as string[]) : [];
|
|
128
|
+
const note = typeof payload.note === 'string' ? payload.note : null;
|
|
129
|
+
|
|
130
|
+
if (css.length === 0) return { installed: false, sources, note };
|
|
131
|
+
|
|
132
|
+
if (!supportsCssScope()) {
|
|
133
|
+
const refusal =
|
|
134
|
+
'unstyled — this browser has no CSS `@scope`, so the game stylesheet cannot be contained ' +
|
|
135
|
+
'to the game. Loading it anyway would restyle the editor.';
|
|
136
|
+
editorConsole.warn(`[scoped-game-css] ${refusal}`, 'authoring');
|
|
137
|
+
return { installed: false, sources, note: refusal };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
installStyleElement(projectRoot, css);
|
|
141
|
+
return { installed: true, sources, note };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Attach (or replace) the ONE style element carrying the project's game CSS. */
|
|
145
|
+
function installStyleElement(projectRoot: string, css: string): void {
|
|
146
|
+
const existing = document.head.querySelector(`style[${STYLE_ELEMENT_MARKER}]`);
|
|
147
|
+
const element = existing instanceof HTMLStyleElement ? existing : document.createElement('style');
|
|
148
|
+
element.setAttribute(STYLE_ELEMENT_MARKER, projectRoot);
|
|
149
|
+
element.textContent = css;
|
|
150
|
+
if (!element.isConnected) document.head.appendChild(element);
|
|
151
|
+
installedCss = css;
|
|
152
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE NOTE A DYING TAB LEAVES FOR THE NEXT ONE.
|
|
3
|
+
*
|
|
4
|
+
* When an editor page decides its server is gone it raises an overlay — and
|
|
5
|
+
* that overlay is the only place the fact has ever existed. Close the tab, or
|
|
6
|
+
* let the browser discard it, and the entire episode is unrecoverable: the
|
|
7
|
+
* server it would have been reported to is precisely the thing that died, so
|
|
8
|
+
* no journal, no `vgai console` and no later session has any record that a
|
|
9
|
+
* window sat there orphaned. A human who steps away and comes back to a
|
|
10
|
+
* relaunched editor is left to reconstruct it from how slow things felt.
|
|
11
|
+
*
|
|
12
|
+
* Measured 2026-08-19: that reconstruction cost about an hour.
|
|
13
|
+
*
|
|
14
|
+
* So the page writes the fact somewhere its dead server cannot reach and
|
|
15
|
+
* cannot erase — `localStorage`, keyed by ORIGIN, which is exactly the scope of
|
|
16
|
+
* "the server that was serving this address". The next editor to boot on that
|
|
17
|
+
* address picks the note up, says it out loud once through the ordinary console
|
|
18
|
+
* door (so it reaches `vgai console` and the session journal like every other
|
|
19
|
+
* editor fact), and clears it.
|
|
20
|
+
*
|
|
21
|
+
* The split here is deliberate: everything that decides or formats is a pure
|
|
22
|
+
* function over plain data, and the two functions that touch `localStorage` do
|
|
23
|
+
* nothing else. That is what lets the whole behaviour be tested without a
|
|
24
|
+
* browser, and it is why the storage handle is a parameter rather than a
|
|
25
|
+
* global reach.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Why the tab that wrote this note stopped being a live editor. Only the
|
|
29
|
+
* UNGRACEFUL deaths are worth a note: a session the user closed on purpose is
|
|
30
|
+
* not a mystery anybody needs solved later. */
|
|
31
|
+
export type OrphanReason = 'server-gone';
|
|
32
|
+
|
|
33
|
+
export interface SessionOrphanRecord {
|
|
34
|
+
/** When the page decided it, epoch ms. */
|
|
35
|
+
readonly at: number;
|
|
36
|
+
readonly reason: OrphanReason;
|
|
37
|
+
/** The project the dead session was serving, as the tab last knew it.
|
|
38
|
+
* `null` when the tab never got a successful identity poll. */
|
|
39
|
+
readonly project: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One key, versioned, namespaced like the editor's other per-user state.
|
|
43
|
+
* `localStorage` is already per-origin, so the key needs no port in it — and
|
|
44
|
+
* must not have one, or a note would be invisible to the very session that
|
|
45
|
+
* replaced the writer. */
|
|
46
|
+
export const SESSION_ORPHAN_KEY = 'vgai.editor.orphaned-tab.v1';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse a stored note, rejecting anything that is not one.
|
|
50
|
+
*
|
|
51
|
+
* Strict and silent: this reads a value any page on the origin could have
|
|
52
|
+
* written, and a boot path is the wrong place to throw. Unusable input is
|
|
53
|
+
* simply "no note" — the same answer as an empty slot, which is the honest one
|
|
54
|
+
* since a malformed note tells us nothing about any session.
|
|
55
|
+
*/
|
|
56
|
+
export function decodeSessionOrphanRecord(raw: string | null): SessionOrphanRecord | null {
|
|
57
|
+
if (!raw) return null;
|
|
58
|
+
let parsed: unknown;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(raw);
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (typeof parsed !== 'object' || parsed === null) return null;
|
|
65
|
+
const record = parsed as Partial<SessionOrphanRecord>;
|
|
66
|
+
if (typeof record.at !== 'number' || !Number.isFinite(record.at)) return null;
|
|
67
|
+
if (record.reason !== 'server-gone') return null;
|
|
68
|
+
const project = typeof record.project === 'string' ? record.project : null;
|
|
69
|
+
return { at: record.at, reason: record.reason, project };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function encodeSessionOrphanRecord(record: SessionOrphanRecord): string {
|
|
73
|
+
return JSON.stringify(record);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The sentence the next session says.
|
|
78
|
+
*
|
|
79
|
+
* It names the three things a reader needs and cannot otherwise get: WHEN the
|
|
80
|
+
* previous window lost its server, WHICH project it was on, and — the part
|
|
81
|
+
* that closes the hour of misdiagnosis — that the stale window is still open
|
|
82
|
+
* and is not the one they are now looking at.
|
|
83
|
+
*/
|
|
84
|
+
export function describeSessionOrphan(record: SessionOrphanRecord, now: number): string {
|
|
85
|
+
const ageSeconds = Math.max(0, Math.round((now - record.at) / 1000));
|
|
86
|
+
const ago =
|
|
87
|
+
ageSeconds < 90
|
|
88
|
+
? `${ageSeconds}s ago`
|
|
89
|
+
: ageSeconds < 5400
|
|
90
|
+
? `${Math.round(ageSeconds / 60)}m ago`
|
|
91
|
+
: `${Math.round(ageSeconds / 3600)}h ago`;
|
|
92
|
+
const project = record.project ? ` on ${record.project}` : '';
|
|
93
|
+
return (
|
|
94
|
+
`A previous editor window at this address lost its server ${ago}${project} and was left ` +
|
|
95
|
+
'orphaned. If that window is still open it is a stale snapshot — nothing in it is saved and ' +
|
|
96
|
+
'every click in it hangs on retries. Close it; this window is the live one.'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* How long a note stays worth saying, ms.
|
|
102
|
+
*
|
|
103
|
+
* A note is about the session the reader is replacing, so it goes stale fast:
|
|
104
|
+
* a day later it is archaeology, and reporting it would train people to ignore
|
|
105
|
+
* the message that matters. Twelve hours covers "I came back the next
|
|
106
|
+
* morning" without reaching into last week.
|
|
107
|
+
*/
|
|
108
|
+
export const SESSION_ORPHAN_MAX_AGE_MS = 12 * 60 * 60 * 1000;
|
|
109
|
+
|
|
110
|
+
/** Whether a note is recent enough for the booting session to say out loud.
|
|
111
|
+
* A note from the FUTURE (a clock the user moved) is not usable evidence
|
|
112
|
+
* either, and is dropped by the same bar. */
|
|
113
|
+
export function sessionOrphanIsWorthReporting(record: SessionOrphanRecord, now: number): boolean {
|
|
114
|
+
const age = now - record.at;
|
|
115
|
+
return age >= 0 && age <= SESSION_ORPHAN_MAX_AGE_MS;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The storage this module needs — narrowed to the three calls it makes so a
|
|
119
|
+
* test can pass a plain object and so no caller is tempted to hand it the
|
|
120
|
+
* whole `Window`. */
|
|
121
|
+
export interface OrphanRecordStorage {
|
|
122
|
+
getItem(key: string): string | null;
|
|
123
|
+
setItem(key: string, value: string): void;
|
|
124
|
+
removeItem(key: string): void;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The page's own `localStorage`, or `null` where there is none (SSR, jsdom
|
|
128
|
+
* without storage, a browser refusing it in private mode). Every caller
|
|
129
|
+
* tolerates `null`: a note is a diagnostic, never a precondition. */
|
|
130
|
+
export function defaultOrphanRecordStorage(): OrphanRecordStorage | null {
|
|
131
|
+
try {
|
|
132
|
+
return typeof localStorage === 'undefined' ? null : localStorage;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Leave the note. Called at the moment the page raises its "server is gone"
|
|
140
|
+
* overlay, so the durable record and the visible one are the same event.
|
|
141
|
+
*
|
|
142
|
+
* A write failure is swallowed on purpose — a full or blocked `localStorage`
|
|
143
|
+
* must not stop the page from telling the user what happened, which is the
|
|
144
|
+
* half that actually matters.
|
|
145
|
+
*/
|
|
146
|
+
export function writeSessionOrphanRecord(
|
|
147
|
+
record: SessionOrphanRecord,
|
|
148
|
+
storage: OrphanRecordStorage | null = defaultOrphanRecordStorage(),
|
|
149
|
+
): void {
|
|
150
|
+
try {
|
|
151
|
+
storage?.setItem(SESSION_ORPHAN_KEY, encodeSessionOrphanRecord(record));
|
|
152
|
+
} catch {
|
|
153
|
+
/* diagnostics never break the page — see above */
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Read and CLEAR the note in one step.
|
|
159
|
+
*
|
|
160
|
+
* Taking rather than peeking is the whole contract: the note describes one
|
|
161
|
+
* episode, so it must be said once. A read that left it behind would have every
|
|
162
|
+
* subsequent boot repeating a stale warning until the user learned to ignore
|
|
163
|
+
* the channel.
|
|
164
|
+
*/
|
|
165
|
+
export function takeSessionOrphanRecord(
|
|
166
|
+
storage: OrphanRecordStorage | null = defaultOrphanRecordStorage(),
|
|
167
|
+
): SessionOrphanRecord | null {
|
|
168
|
+
if (!storage) return null;
|
|
169
|
+
try {
|
|
170
|
+
const record = decodeSessionOrphanRecord(storage.getItem(SESSION_ORPHAN_KEY));
|
|
171
|
+
storage.removeItem(SESSION_ORPHAN_KEY);
|
|
172
|
+
return record;
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Drop the note without reporting it.
|
|
180
|
+
*
|
|
181
|
+
* The `resume` branch: the same process answered again, so the page was never
|
|
182
|
+
* orphaned and the note it wrote a moment ago is simply wrong. Retracting it
|
|
183
|
+
* here is what keeps the record trustworthy enough to act on.
|
|
184
|
+
*/
|
|
185
|
+
export function clearSessionOrphanRecord(
|
|
186
|
+
storage: OrphanRecordStorage | null = defaultOrphanRecordStorage(),
|
|
187
|
+
): void {
|
|
188
|
+
try {
|
|
189
|
+
storage?.removeItem(SESSION_ORPHAN_KEY);
|
|
190
|
+
} catch {
|
|
191
|
+
/* see writeSessionOrphanRecord */
|
|
192
|
+
}
|
|
193
|
+
}
|