@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,628 @@
|
|
|
1
|
+
/* THE TAB BOOTSTRAP — what makes a page one of this session's TABS.
|
|
2
|
+
*
|
|
3
|
+
* It mints the tab identity, opens the duplex control channel, starts the
|
|
4
|
+
* heartbeat WORKER that proves the tab to `tab-presence.ts`, answers the
|
|
5
|
+
* main-thread echo, and sends the `pagehide` goodbye. Everything the tab
|
|
6
|
+
* bijection decides — bless, yield, depart, re-mint — is decided from what
|
|
7
|
+
* this sends.
|
|
8
|
+
*
|
|
9
|
+
* IT IS PLAIN JS, AND IT IS SERVED VERBATIM (`tab-bootstrap.ts`, at
|
|
10
|
+
* `/__editor/tab-bootstrap.js`). The page that runs it is the Code-OSS
|
|
11
|
+
* workbench's, authored by VS Code, so it cannot be a module of the editor's
|
|
12
|
+
* graph and cannot be transformed: it is a classic script the contribution
|
|
13
|
+
* loads BEFORE the React Refresh preamble and before the editor itself. It
|
|
14
|
+
* takes its base from its own `src` (`document.currentScript`), so every url
|
|
15
|
+
* it builds points at the session rather than at the page hosting it.
|
|
16
|
+
*
|
|
17
|
+
* It lived inline in the editor's own `index.html` until PART B, and was read
|
|
18
|
+
* back out of that file to be served here; the page is gone and this is its
|
|
19
|
+
* own file now, with one author and nothing to drift against.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/* Connect the tab before any editor module loads. `editor-presence.ts` adopts
|
|
23
|
+
this exact client id/socket and replays any lifecycle event that arrived
|
|
24
|
+
while its typed handlers were loading. */
|
|
25
|
+
(() => {
|
|
26
|
+
/* ONCE PER DOCUMENT. The mount path that loads this can be reached twice,
|
|
27
|
+
and a second bootstrap would mint a second tab identity for one document:
|
|
28
|
+
two tabs, to a table that exists to count one. */
|
|
29
|
+
if (globalThis.__VGAI_EDITOR_PRESENCE_BOOTSTRAP__) return;
|
|
30
|
+
|
|
31
|
+
/* WHERE THE SESSION IS — the base every url below is built on, and the
|
|
32
|
+
reason none of them reads `location`.
|
|
33
|
+
|
|
34
|
+
Inline in the editor's own page the two are the same thing: an inline
|
|
35
|
+
script's `currentScript` has no `src`, so this falls through to
|
|
36
|
+
`location.href` and nothing changes. But this same bootstrap is also
|
|
37
|
+
SERVED, as `/__editor/tab-bootstrap.js`, to a page vgai does not
|
|
38
|
+
author — the Code-OSS workbench, which on desktop is
|
|
39
|
+
`vscode-file://vscode-app` and can never be this session's origin
|
|
40
|
+
(docs/CODE-OSS.md §Boot, DESKTOP; server/tab-bootstrap.ts). On that
|
|
41
|
+
page `location` is the WORKBENCH: a heartbeat socket built from it
|
|
42
|
+
goes nowhere, and a tab that cannot beat is a tab the bijection
|
|
43
|
+
believes is dead. The script's own `src` is the one reading that
|
|
44
|
+
cannot be wrong, because the frame fetched it from the session.
|
|
45
|
+
|
|
46
|
+
The frame also declares WHAT KIND of page it is in the same url
|
|
47
|
+
(`?surface=vscode-desktop`), and that is what the tab table reports
|
|
48
|
+
back — so a page that beats is never described as an anomaly. */
|
|
49
|
+
const bootstrapSrc = document.currentScript?.src || '';
|
|
50
|
+
const sessionBase = bootstrapSrc ? `${new URL(bootstrapSrc).origin}/` : location.href;
|
|
51
|
+
const pageSurface = bootstrapSrc ? new URL(bootstrapSrc).searchParams.get('surface') || '' : '';
|
|
52
|
+
const sessionUrl = (path) => new URL(path, sessionBase).href;
|
|
53
|
+
const sessionSocketUrl = (path) => {
|
|
54
|
+
const url = new URL(path, sessionBase);
|
|
55
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
56
|
+
return url;
|
|
57
|
+
};
|
|
58
|
+
const clientId = globalThis.crypto?.randomUUID?.() || `editor-${Date.now()}`;
|
|
59
|
+
const uuid = () => globalThis.crypto?.randomUUID?.() || `id-${Date.now()}-${Math.random()}`;
|
|
60
|
+
|
|
61
|
+
/* PAGE ERRORS, CAPTURED FIRST — before this bootstrap's own next line,
|
|
62
|
+
and long before the module graph.
|
|
63
|
+
|
|
64
|
+
The doctrine: the product answers its own state. Every other door onto
|
|
65
|
+
a tab's errors lives INSIDE the module graph (editor-console.ts's
|
|
66
|
+
capture, read back by command-listener.ts's `collectPageErrors`), so
|
|
67
|
+
the one boot failure that most needs explaining — the one that kills
|
|
68
|
+
the page before the command listener attaches — was the only one the
|
|
69
|
+
product could not describe. It could report `commandListener: not
|
|
70
|
+
attached` and never why, and the way you learned the cause was to open
|
|
71
|
+
devtools, which is the workaround this whole seam exists to delete.
|
|
72
|
+
|
|
73
|
+
These go out on the control connection the bootstrap already holds,
|
|
74
|
+
falling back to a POST exactly like editor-presence.ts's `sendControl`
|
|
75
|
+
does for a tunnelled tab. The server journals them and serves them
|
|
76
|
+
back on `/__editor/state` — a plain GET — so `vgai status` answers with
|
|
77
|
+
no listener, no module graph, and no cooperation from the page beyond
|
|
78
|
+
this handler. */
|
|
79
|
+
const pendingPageErrors = [];
|
|
80
|
+
let pageErrorChannel = null;
|
|
81
|
+
const PAGE_ERROR_LIMIT = 8;
|
|
82
|
+
const deliverPageError = (message) => {
|
|
83
|
+
if (pageErrorChannel && pageErrorChannel.send('page-error', { message })) return;
|
|
84
|
+
try {
|
|
85
|
+
const lifecycle = pageErrorChannel?.lifecycle ?? null;
|
|
86
|
+
void fetch(sessionUrl('/__editor/page-error'), {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
message,
|
|
91
|
+
_clientId: clientId,
|
|
92
|
+
...(lifecycle === null ? {} : { _controlLifecycle: lifecycle }),
|
|
93
|
+
}),
|
|
94
|
+
keepalive: true,
|
|
95
|
+
}).catch(() => {});
|
|
96
|
+
} catch {
|
|
97
|
+
/* A page that cannot even fetch has nothing left to report with;
|
|
98
|
+
the tab table still says the document never came up. */
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const flushPageErrors = () => {
|
|
102
|
+
while (pendingPageErrors.length > 0) deliverPageError(pendingPageErrors.shift());
|
|
103
|
+
};
|
|
104
|
+
const capturePageError = (message) => {
|
|
105
|
+
if (typeof message !== 'string' || message === '') return;
|
|
106
|
+
if (pageErrorChannel === null) {
|
|
107
|
+
if (pendingPageErrors.length < PAGE_ERROR_LIMIT) pendingPageErrors.push(message);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
deliverPageError(message);
|
|
111
|
+
};
|
|
112
|
+
const describeError = (value) => {
|
|
113
|
+
if (value instanceof Error) return `${value.name}: ${value.message}\n${value.stack ?? ''}`;
|
|
114
|
+
try {
|
|
115
|
+
return String(value);
|
|
116
|
+
} catch {
|
|
117
|
+
return 'unstringifiable error value';
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
window.addEventListener(
|
|
121
|
+
'error',
|
|
122
|
+
(event) => {
|
|
123
|
+
/* A failed <script>/<link> load raises `error` on the ELEMENT with no
|
|
124
|
+
`error` property — the module graph failing to arrive at all, which
|
|
125
|
+
is precisely the case that leaves no listener. */
|
|
126
|
+
if (event.error) capturePageError(describeError(event.error));
|
|
127
|
+
else if (event.message)
|
|
128
|
+
capturePageError(`${event.message} (${event.filename ?? '?'}:${event.lineno ?? 0})`);
|
|
129
|
+
else if (event.target && event.target !== window) {
|
|
130
|
+
const src = event.target.src || event.target.href;
|
|
131
|
+
if (src) capturePageError(`failed to load ${src}`);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
true,
|
|
135
|
+
);
|
|
136
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
137
|
+
capturePageError(`unhandled rejection: ${describeError(event.reason)}`);
|
|
138
|
+
});
|
|
139
|
+
/* TAB IDENTITY. sessionStorage is scoped to exactly one tab and
|
|
140
|
+
survives its reloads, which is the definition the server needs: a
|
|
141
|
+
reload is the SAME tab with a new `epoch`, not a departure. The one
|
|
142
|
+
thing it gets wrong is "Duplicate Tab", which COPIES it — the server
|
|
143
|
+
sees two epochs beating under one tabId and tells the younger to
|
|
144
|
+
re-mint, which is what `remint()` below does. */
|
|
145
|
+
const TAB_KEY = 'vgai.tab.v1';
|
|
146
|
+
let tabId;
|
|
147
|
+
try {
|
|
148
|
+
tabId = sessionStorage.getItem(TAB_KEY) || '';
|
|
149
|
+
if (!tabId) {
|
|
150
|
+
tabId = uuid();
|
|
151
|
+
sessionStorage.setItem(TAB_KEY, tabId);
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
/* No sessionStorage (rare, but a private-mode quirk is not a reason
|
|
155
|
+
to be invisible): a per-load id still makes this tab countable. */
|
|
156
|
+
tabId = uuid();
|
|
157
|
+
}
|
|
158
|
+
const epoch = uuid();
|
|
159
|
+
let participantId;
|
|
160
|
+
let displayName = 'Local editor';
|
|
161
|
+
try {
|
|
162
|
+
participantId = document.cookie
|
|
163
|
+
.split('; ')
|
|
164
|
+
.find((entry) => entry.startsWith('vgai_share_participant='))
|
|
165
|
+
?.slice('vgai_share_participant='.length);
|
|
166
|
+
participantId ||= localStorage.getItem('vgai.collaboration.participant.v1');
|
|
167
|
+
if (!participantId) {
|
|
168
|
+
participantId = globalThis.crypto?.randomUUID?.() || `participant-${Date.now()}`;
|
|
169
|
+
localStorage.setItem('vgai.collaboration.participant.v1', participantId);
|
|
170
|
+
}
|
|
171
|
+
displayName = localStorage.getItem('vgai.collaboration.display-name.v1') || displayName;
|
|
172
|
+
} catch {
|
|
173
|
+
participantId = globalThis.crypto?.randomUUID?.() || `participant-${Date.now()}`;
|
|
174
|
+
}
|
|
175
|
+
const query = new URLSearchParams({
|
|
176
|
+
clientId,
|
|
177
|
+
participantId,
|
|
178
|
+
displayName,
|
|
179
|
+
tabId,
|
|
180
|
+
pageGeneration: epoch,
|
|
181
|
+
});
|
|
182
|
+
/* Only a page that is not the editor's own sends this; the tab table
|
|
183
|
+
keeps it so `vgai status` can name the page instead of guessing. */
|
|
184
|
+
if (pageSurface) query.set('surface', pageSurface);
|
|
185
|
+
/* The tab's control channel. Downstream it looks exactly like an
|
|
186
|
+
EventSource (same named events, same `data` strings). Upstream it
|
|
187
|
+
also carries this page's control replies — receipts, results, state,
|
|
188
|
+
route — so they never queue behind a module flood in the browser's
|
|
189
|
+
per-origin HTTP connection pool. That queueing is what made a cold
|
|
190
|
+
boot's blocked main thread indistinguishable from a dead tab on
|
|
191
|
+
2026-08-09: five `vgai play` commands were refused as "not picked
|
|
192
|
+
up" and every one of them ran later.
|
|
193
|
+
|
|
194
|
+
`duplex` is granted by the SERVER, never assumed: the share tunnel's
|
|
195
|
+
gateway answers this same URL with a one-way bridge of the SSE
|
|
196
|
+
stream, and a control frame sent into that would vanish. Only a
|
|
197
|
+
native socket emits `control-duplex`, so a tunnelled tab keeps
|
|
198
|
+
POSTing (see editor-presence.ts's `sendControl`). */
|
|
199
|
+
class WebSocketEventSource extends EventTarget {
|
|
200
|
+
constructor(path) {
|
|
201
|
+
super();
|
|
202
|
+
this.path = path;
|
|
203
|
+
this.closed = false;
|
|
204
|
+
this.duplex = false;
|
|
205
|
+
this.lifecycle = null;
|
|
206
|
+
this.lastEventId = '';
|
|
207
|
+
this.retryMs = 500;
|
|
208
|
+
this.connectionAttempt = 0;
|
|
209
|
+
this.reidentifyAfterClose = null;
|
|
210
|
+
this.identityReady = null;
|
|
211
|
+
this.connect();
|
|
212
|
+
}
|
|
213
|
+
connect() {
|
|
214
|
+
const attempt = ++this.connectionAttempt;
|
|
215
|
+
const url = new URL(this.path, sessionBase);
|
|
216
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
217
|
+
if (this.lastEventId) url.searchParams.set('lastEventId', this.lastEventId);
|
|
218
|
+
const socket = new WebSocket(url);
|
|
219
|
+
this.socket = socket;
|
|
220
|
+
socket.addEventListener('open', () => {
|
|
221
|
+
if (attempt !== this.connectionAttempt) return;
|
|
222
|
+
this.retryMs = 500;
|
|
223
|
+
this.dispatchEvent(new Event('open'));
|
|
224
|
+
});
|
|
225
|
+
socket.addEventListener('message', (message) => {
|
|
226
|
+
if (attempt !== this.connectionAttempt || this.reidentifyAfterClose !== null) return;
|
|
227
|
+
try {
|
|
228
|
+
const frame = JSON.parse(String(message.data));
|
|
229
|
+
if (!frame || typeof frame.event !== 'string' || typeof frame.data !== 'string') {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (frame.event === 'control-duplex') {
|
|
233
|
+
this.duplex = true;
|
|
234
|
+
this.finishIdentityChange();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (frame.event === 'control-lifecycle') {
|
|
238
|
+
const lifecycle = JSON.parse(frame.data);
|
|
239
|
+
if (
|
|
240
|
+
lifecycle?.version === 1 &&
|
|
241
|
+
typeof lifecycle.serverGeneration === 'string' &&
|
|
242
|
+
typeof lifecycle.connectionGeneration === 'string' &&
|
|
243
|
+
typeof lifecycle.clientId === 'string' &&
|
|
244
|
+
typeof lifecycle.tabId === 'string' &&
|
|
245
|
+
typeof lifecycle.pageGeneration === 'string'
|
|
246
|
+
) {
|
|
247
|
+
this.lifecycle = lifecycle;
|
|
248
|
+
this.dispatchEvent(new MessageEvent('control-lifecycle', { data: frame.data }));
|
|
249
|
+
this.finishIdentityChange();
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
/* The main-thread liveness echo. Answered HERE, in the inline
|
|
254
|
+
bootstrap, because that is what makes an unanswered echo
|
|
255
|
+
mean something: this responder exists before the first
|
|
256
|
+
module loads, so silence can only be a blocked main thread —
|
|
257
|
+
never a listener that had not been wired up yet. */
|
|
258
|
+
if (frame.event === 'control-echo') {
|
|
259
|
+
this.send('echo-reply', { id: frame.data });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (typeof frame.id === 'string') this.lastEventId = frame.id;
|
|
263
|
+
this.dispatchEvent(
|
|
264
|
+
new MessageEvent(frame.event, {
|
|
265
|
+
data: frame.data,
|
|
266
|
+
lastEventId: typeof frame.id === 'string' ? frame.id : '',
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
269
|
+
} catch {
|
|
270
|
+
this.dispatchEvent(new Event('error'));
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
socket.addEventListener('error', () => this.dispatchEvent(new Event('error')));
|
|
274
|
+
socket.addEventListener('close', () => {
|
|
275
|
+
if (attempt !== this.connectionAttempt) return;
|
|
276
|
+
this.duplex = false;
|
|
277
|
+
this.lifecycle = null;
|
|
278
|
+
if (this.closed) return;
|
|
279
|
+
if (this.reidentifyAfterClose !== null) {
|
|
280
|
+
this.path = this.reidentifyAfterClose;
|
|
281
|
+
this.reidentifyAfterClose = null;
|
|
282
|
+
this.retryMs = 500;
|
|
283
|
+
this.connect();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const retryMs = this.retryMs;
|
|
287
|
+
this.retryMs = Math.min(5_000, retryMs * 2);
|
|
288
|
+
/* Same clientId across reconnects — the server treats the
|
|
289
|
+
reconnected tab as the same tab, not a second one. */
|
|
290
|
+
setTimeout(() => this.connect(), retryMs);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/* Is this page's control connection to the server UP right now?
|
|
294
|
+
Read by the supervised lease (src/editor-lease.ts) as the second,
|
|
295
|
+
independent liveness reading: a killed server drops this socket
|
|
296
|
+
immediately, while a merely BUSY one — a blocked event loop, a cold
|
|
297
|
+
dep-optimize — holds it open while HTTP polls time out. Without it
|
|
298
|
+
the watchdog cannot tell "gone" from "slow", and on 2026-08-09 a
|
|
299
|
+
`kill -STOP`ped (perfectly alive) server produced the blocking
|
|
300
|
+
"Editor disconnected" scrim while this socket read OPEN. */
|
|
301
|
+
get connected() {
|
|
302
|
+
return this.socket?.readyState === WebSocket.OPEN;
|
|
303
|
+
}
|
|
304
|
+
/* Upstream control frame; false when this socket cannot carry it.
|
|
305
|
+
The envelope is built HERE and only here, with the discriminator
|
|
306
|
+
and the payload in separate keys — a caller cannot flatten them.
|
|
307
|
+
A payload is an open-ended object (a state snapshot is literally
|
|
308
|
+
whatever the editor reports), so a shared namespace would let a
|
|
309
|
+
key named `type` overwrite the frame type on the way out and drop
|
|
310
|
+
the whole report, and would leak the frame type into stored state
|
|
311
|
+
on the way in. */
|
|
312
|
+
send(type, payload) {
|
|
313
|
+
const socket = this.socket;
|
|
314
|
+
if (!this.duplex || !this.lifecycle || !socket || socket.readyState !== WebSocket.OPEN)
|
|
315
|
+
return false;
|
|
316
|
+
try {
|
|
317
|
+
socket.send(JSON.stringify({ type, payload, lifecycle: this.lifecycle }));
|
|
318
|
+
return true;
|
|
319
|
+
} catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
finishIdentityChange() {
|
|
324
|
+
if (!this.duplex || !this.lifecycle || this.identityReady === null) return;
|
|
325
|
+
const ready = this.identityReady;
|
|
326
|
+
this.identityReady = null;
|
|
327
|
+
ready();
|
|
328
|
+
}
|
|
329
|
+
/* Replace ALL channels which name a tab, as one transition. The old
|
|
330
|
+
connection closes before the new one opens, and the new heartbeat
|
|
331
|
+
starts only after the server accepts the replacement control
|
|
332
|
+
identity. A duplicate can therefore never beat as B while taking
|
|
333
|
+
commands as copied tab A. */
|
|
334
|
+
reidentifyTab(nextTabId, ready) {
|
|
335
|
+
const next = new URL(this.path, sessionBase);
|
|
336
|
+
next.searchParams.set('tabId', nextTabId);
|
|
337
|
+
this.duplex = false;
|
|
338
|
+
this.lifecycle = null;
|
|
339
|
+
this.identityReady = ready;
|
|
340
|
+
this.reidentifyAfterClose = `${next.pathname}${next.search}${next.hash}`;
|
|
341
|
+
const socket = this.socket;
|
|
342
|
+
if (socket && socket.readyState !== WebSocket.CLOSED) {
|
|
343
|
+
try {
|
|
344
|
+
socket.close(1000, 'tab identity replaced');
|
|
345
|
+
return;
|
|
346
|
+
} catch {
|
|
347
|
+
/* Fall through to a fresh attempt. */
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
this.path = this.reidentifyAfterClose;
|
|
351
|
+
this.reidentifyAfterClose = null;
|
|
352
|
+
this.connect();
|
|
353
|
+
}
|
|
354
|
+
close() {
|
|
355
|
+
this.closed = true;
|
|
356
|
+
this.socket?.close();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/* Every tab, not just tunnelled ones. TryCloudflare Quick Tunnels
|
|
360
|
+
cannot do SSE at all; local tabs use the socket for the reason
|
|
361
|
+
above. The plain GET/SSE form of this URL still works — it is what
|
|
362
|
+
the tunnel gateway bridges through, and what curl reads. */
|
|
363
|
+
let bootstrap = null;
|
|
364
|
+
const source = new WebSocketEventSource(`/__editor/events?${query}`);
|
|
365
|
+
/* From here the capture above has a channel. Anything it buffered while
|
|
366
|
+
this class was being defined goes now, and on every reconnect — the
|
|
367
|
+
duplex grant arrives after `open`, and `send` answers false until it
|
|
368
|
+
does, which is exactly when the POST fallback takes over. */
|
|
369
|
+
pageErrorChannel = source;
|
|
370
|
+
source.addEventListener('open', () => flushPageErrors());
|
|
371
|
+
flushPageErrors();
|
|
372
|
+
|
|
373
|
+
/* THE HEARTBEAT. A dedicated Worker, started here — before the module
|
|
374
|
+
graph exists — because the failure it covers is the module graph
|
|
375
|
+
blocking the main thread for a minute on a cold boot. It keeps
|
|
376
|
+
beating through that, it dies exactly when this tab dies, and its
|
|
377
|
+
socket is not this page's socket, so nothing that happens to the
|
|
378
|
+
control channel can make the server believe the tab went away.
|
|
379
|
+
|
|
380
|
+
Presence is a UNION of a fresh beat and a live control channel
|
|
381
|
+
(server/tab-presence.ts), so a tab that cannot run a worker at all —
|
|
382
|
+
a tunnelled one, which is refused the script by the share gateway —
|
|
383
|
+
is still SEEN. What the beat adds is the case the union needs it
|
|
384
|
+
for: a tab whose page socket is down or whose main thread is
|
|
385
|
+
blocked. */
|
|
386
|
+
let heartbeatWorker = null;
|
|
387
|
+
let heartbeatVisibilityListener = null;
|
|
388
|
+
const startHeartbeat = (activeTabId) => {
|
|
389
|
+
try {
|
|
390
|
+
heartbeatWorker?.terminate();
|
|
391
|
+
if (heartbeatVisibilityListener !== null) {
|
|
392
|
+
document.removeEventListener('visibilitychange', heartbeatVisibilityListener);
|
|
393
|
+
}
|
|
394
|
+
const worker = new Worker(sessionUrl('/__editor/tab-heartbeat.js'));
|
|
395
|
+
heartbeatWorker = worker;
|
|
396
|
+
worker.addEventListener('message', (event) => {
|
|
397
|
+
if (event.data?.type === 're-mint') remint();
|
|
398
|
+
});
|
|
399
|
+
/* A worker that cannot start (no script — the share gateway
|
|
400
|
+
refuses it to a tunnelled tab — or a CSP that forbids one) is
|
|
401
|
+
not an error to report: presence falls back to this page's own
|
|
402
|
+
control channel. */
|
|
403
|
+
worker.addEventListener('error', (event) => event.preventDefault());
|
|
404
|
+
const socketUrl = sessionSocketUrl('/__editor/heartbeat');
|
|
405
|
+
socketUrl.searchParams.set('tabId', activeTabId);
|
|
406
|
+
worker.postMessage({
|
|
407
|
+
type: 'start',
|
|
408
|
+
tabId: activeTabId,
|
|
409
|
+
epoch,
|
|
410
|
+
socketUrl: socketUrl.href,
|
|
411
|
+
postUrl: sessionUrl('/__editor/heartbeat'),
|
|
412
|
+
});
|
|
413
|
+
const reportVisibility = () =>
|
|
414
|
+
worker.postMessage({
|
|
415
|
+
type: 'visibility',
|
|
416
|
+
visibility: document.visibilityState === 'hidden' ? 'hidden' : 'visible',
|
|
417
|
+
});
|
|
418
|
+
heartbeatVisibilityListener = reportVisibility;
|
|
419
|
+
document.addEventListener('visibilitychange', heartbeatVisibilityListener);
|
|
420
|
+
reportVisibility();
|
|
421
|
+
} catch {
|
|
422
|
+
/* A tab that cannot run a Worker is still SEEN — the server counts
|
|
423
|
+
a live control channel as presence too (tab-presence.ts's union
|
|
424
|
+
rule). It just loses the reload/blocked-thread coverage. */
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
/* "Duplicate Tab" copied this tab's sessionStorage, so two pages are
|
|
428
|
+
beating under one identity. The younger takes a fresh one. */
|
|
429
|
+
const remint = () => {
|
|
430
|
+
heartbeatWorker?.terminate();
|
|
431
|
+
heartbeatWorker = null;
|
|
432
|
+
tabId = uuid();
|
|
433
|
+
try {
|
|
434
|
+
sessionStorage.setItem(TAB_KEY, tabId);
|
|
435
|
+
} catch {
|
|
436
|
+
/* the in-memory id is enough for this page load */
|
|
437
|
+
}
|
|
438
|
+
if (bootstrap !== null) bootstrap.tabId = tabId;
|
|
439
|
+
source.reidentifyTab(tabId, () => startHeartbeat(tabId));
|
|
440
|
+
};
|
|
441
|
+
startHeartbeat(tabId);
|
|
442
|
+
|
|
443
|
+
/* The module graph's one door to the beat. The census rides the
|
|
444
|
+
HEARTBEAT rather than the control channel deliberately: the profile
|
|
445
|
+
that explains a death has to leave the tab on the thread that is
|
|
446
|
+
still alive when the page's is not.
|
|
447
|
+
|
|
448
|
+
Each call hands over ONE fresh sample and the worker carries it on one
|
|
449
|
+
beat, so the server's stamp means "the page sampled this" — never
|
|
450
|
+
"the worker echoed it again". Nothing is replayed into a re-minted
|
|
451
|
+
worker for the same reason: a duplicate-tab re-mint is a new tabId
|
|
452
|
+
with an empty record, and it gets its first honest sample within 5s. */
|
|
453
|
+
const reportCensus = (census) => {
|
|
454
|
+
try {
|
|
455
|
+
heartbeatWorker?.postMessage({ type: 'census', census });
|
|
456
|
+
} catch {
|
|
457
|
+
/* the next sample tries again; a missed profile is not a missed beat */
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
/* What the page's main thread is about to do — a model's build() — or
|
|
461
|
+
null when it is done. Same road as the census, for the same reason:
|
|
462
|
+
the phase that explains a stall has to leave on the thread that is
|
|
463
|
+
still alive while the page's is inside the work. */
|
|
464
|
+
const reportPhase = (phase) => {
|
|
465
|
+
try {
|
|
466
|
+
heartbeatWorker?.postMessage({ type: 'phase', phase });
|
|
467
|
+
} catch {
|
|
468
|
+
/* the control channel carries it too, once the page can speak */
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/* LEAVING THE TAB TABLE, for a page whose session is over for good.
|
|
473
|
+
|
|
474
|
+
The beat is what PROVES a tab to the server (server/tab-presence.ts),
|
|
475
|
+
so a page that keeps beating after its session ended stays present,
|
|
476
|
+
stays blessed, and `vgai edit` answers "focused" instead of opening a
|
|
477
|
+
real tab — a corpse holding the session's one tab slot. Measured on
|
|
478
|
+
this box, 2026-08-15: a page that had painted the "session ended"
|
|
479
|
+
notice went on beating under the same tabId across a `vgai close` and
|
|
480
|
+
a fresh `vgai edit`, so the new server blessed it and every command
|
|
481
|
+
had nowhere to go.
|
|
482
|
+
|
|
483
|
+
Stopping the beat is the whole withdrawal: the table departs the tab
|
|
484
|
+
on the evidence it already uses, and the bijection opens a new one by
|
|
485
|
+
its own existing rule. Only the TERMINAL deaths call this
|
|
486
|
+
(src/session-tombstone.ts) — a `server-gone` page may still resume. */
|
|
487
|
+
const stopHeartbeat = () => {
|
|
488
|
+
try {
|
|
489
|
+
heartbeatWorker?.postMessage({ type: 'stop' });
|
|
490
|
+
if (heartbeatVisibilityListener !== null) {
|
|
491
|
+
document.removeEventListener('visibilitychange', heartbeatVisibilityListener);
|
|
492
|
+
heartbeatVisibilityListener = null;
|
|
493
|
+
}
|
|
494
|
+
} catch {
|
|
495
|
+
/* the worker dies with the document regardless */
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
/* THE GOODBYE. One `sendBeacon` on `pagehide`, and the only page signal
|
|
500
|
+
this bootstrap sends about its own end.
|
|
501
|
+
|
|
502
|
+
WHY (measured 2026-09-17): four different failures — a renderer killed
|
|
503
|
+
by a dev-server reload, a worker that never finished booting, a main
|
|
504
|
+
thread wedged inside a 46 MB encode, and a twin call genuinely running
|
|
505
|
+
for sixteen minutes — all reached the server as the SAME observation,
|
|
506
|
+
beats that stopped or a page that did not answer. `pagehide` fires for
|
|
507
|
+
a close, a navigation and a reload; it does NOT fire for a renderer
|
|
508
|
+
kill or a wedged thread. So its presence is exactly the discriminator
|
|
509
|
+
the table was missing (server/tab-presence.ts's `tabState`): a silence
|
|
510
|
+
WITH a goodbye is closed/reloading, a silence without one is crashed.
|
|
511
|
+
|
|
512
|
+
`sendBeacon` and not `fetch`: it is the one transport the browser
|
|
513
|
+
promises to deliver for a document that is already gone. The reply is
|
|
514
|
+
unreadable by definition, and nothing waits on it — a beacon that does
|
|
515
|
+
not arrive degrades to `crashed`, which is what the server believed
|
|
516
|
+
before this line existed.
|
|
517
|
+
|
|
518
|
+
`persisted` is the back/forward cache: the page may yet come back, so
|
|
519
|
+
it is reported rather than collapsed into "this page is over". */
|
|
520
|
+
const sendCloseBeacon = (persisted, reason) => {
|
|
521
|
+
try {
|
|
522
|
+
navigator.sendBeacon?.(
|
|
523
|
+
sessionUrl('/__editor/tab/close'),
|
|
524
|
+
new Blob(
|
|
525
|
+
[
|
|
526
|
+
JSON.stringify({
|
|
527
|
+
tabId,
|
|
528
|
+
epoch,
|
|
529
|
+
persisted: persisted === true,
|
|
530
|
+
...(reason === undefined ? {} : { reason }),
|
|
531
|
+
}),
|
|
532
|
+
],
|
|
533
|
+
{ type: 'application/json' },
|
|
534
|
+
),
|
|
535
|
+
);
|
|
536
|
+
} catch {
|
|
537
|
+
/* a goodbye that cannot be sent is the crash case, truthfully */
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
addEventListener('pagehide', (event) => sendCloseBeacon(event.persisted));
|
|
541
|
+
|
|
542
|
+
bootstrap = {
|
|
543
|
+
clientId,
|
|
544
|
+
participantId,
|
|
545
|
+
displayName,
|
|
546
|
+
tabId,
|
|
547
|
+
epoch,
|
|
548
|
+
controlLifecycle: source.lifecycle,
|
|
549
|
+
reportCensus,
|
|
550
|
+
reportPhase,
|
|
551
|
+
stopHeartbeat,
|
|
552
|
+
source,
|
|
553
|
+
pageOwned: true,
|
|
554
|
+
pending: [],
|
|
555
|
+
queueListeners: [],
|
|
556
|
+
};
|
|
557
|
+
source.addEventListener('control-lifecycle', (event) => {
|
|
558
|
+
try {
|
|
559
|
+
bootstrap.controlLifecycle = JSON.parse(event.data);
|
|
560
|
+
} catch {
|
|
561
|
+
bootstrap.controlLifecycle = null;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
globalThis.__VGAI_EDITOR_PRESENCE_BOOTSTRAP__ = bootstrap;
|
|
565
|
+
/* This EventSource connects before the module graph loads, so from here
|
|
566
|
+
on the server counts this tab and will route to it. Anything it sends
|
|
567
|
+
in that window reaches a page with no listener for it yet — buffered
|
|
568
|
+
here, replayed by editor-presence.ts once a real consumer subscribes.
|
|
569
|
+
`editor-command` belongs on this list for the same reason the tab-*
|
|
570
|
+
events do, and its absence was a silent command loss: the event
|
|
571
|
+
dispatched to zero listeners and left no trace, so `vgai play` was
|
|
572
|
+
refused for a tab that never saw the command. Capped because a tab
|
|
573
|
+
whose consumer never arrives must not grow this without bound. */
|
|
574
|
+
/* `tab-reload` is the ONE instruction that is not buffered, because it is
|
|
575
|
+
the one that matters precisely when the module graph never evaluated.
|
|
576
|
+
Everything else here waits for a consumer that, in that case, is never
|
|
577
|
+
coming: a page whose app failed to boot keeps beating from its worker,
|
|
578
|
+
keeps this EventSource open, and answers `tab-refocus` and `tab-adopt`
|
|
579
|
+
by appending them to a queue nobody drains. The session then adopts it
|
|
580
|
+
forever and no command can complete. Acting here — in the inline
|
|
581
|
+
bootstrap, before any module — is what makes "a lost tab self-heals"
|
|
582
|
+
true for a lost DOCUMENT and not just a lost socket. */
|
|
583
|
+
source.addEventListener('tab-reload', () => {
|
|
584
|
+
location.reload();
|
|
585
|
+
});
|
|
586
|
+
/* `tab-close` is the SECOND instruction this bootstrap acts on itself, and
|
|
587
|
+
for the same reason as the first: it matters precisely when no editor
|
|
588
|
+
module is there to act on it.
|
|
589
|
+
|
|
590
|
+
Measured 2026-09-21 on a first open of an untrusted folder: the page is a
|
|
591
|
+
tab from the moment it loads (the frame's activation bootstrap) while the
|
|
592
|
+
workbench is still asking whether the person trusts it, so `vgai close`
|
|
593
|
+
reached a tab whose consumer was never coming — the instruction went onto
|
|
594
|
+
a queue nobody drains, the session waited out its two seconds and exited
|
|
595
|
+
with "tab … did not acknowledge", and the browser was left showing a
|
|
596
|
+
workbench whose session is gone. The bijection's own promise is that
|
|
597
|
+
shutdown CLOSES the tab; a window that only draws the editor could not
|
|
598
|
+
keep it.
|
|
599
|
+
|
|
600
|
+
ONLY WHILE NOBODY ELSE OWNS IT. `editor-presence.ts` removes this type's
|
|
601
|
+
queue listener the moment its own consumer attaches (`drainBootstrap`), so
|
|
602
|
+
a still-queued `tab-close` is exactly "the editor has not taken this
|
|
603
|
+
over" — and after it has, this stays out of the way and the editor's
|
|
604
|
+
handler does the whole job (tombstone latch, ack, close-or-card). */
|
|
605
|
+
source.addEventListener('tab-close', () => {
|
|
606
|
+
if (!bootstrap.queueListeners.some((entry) => entry.type === 'tab-close')) return;
|
|
607
|
+
sendCloseBeacon(false, 'session-ended');
|
|
608
|
+
/* Best effort: a browser refuses `close()` for a tab a person opened. The
|
|
609
|
+
page then keeps standing with a dead session behind it — the same
|
|
610
|
+
outcome as before this listener, minus the unacknowledged close.
|
|
611
|
+
`globalThis.close`, spelled out: this file declares a `close()` METHOD a
|
|
612
|
+
few hundred lines up (the control socket's), and a bare call here costs
|
|
613
|
+
the next reader the proof that it is not that one. */
|
|
614
|
+
try {
|
|
615
|
+
globalThis.close?.();
|
|
616
|
+
} catch {
|
|
617
|
+
/* refused is the ordinary case, and it is not an error */
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
for (const type of ['tab-refocus', 'tab-yield', 'tab-close', 'tab-adopt', 'editor-command']) {
|
|
621
|
+
const listener = (event) => {
|
|
622
|
+
if (bootstrap.pending.length >= 64) return;
|
|
623
|
+
bootstrap.pending.push({ type, data: event.data });
|
|
624
|
+
};
|
|
625
|
+
source.addEventListener(type, listener);
|
|
626
|
+
bootstrap.queueListeners.push({ type, listener });
|
|
627
|
+
}
|
|
628
|
+
})();
|