@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,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab lifecycle — the stateful shell around the tab table (`tab-presence.ts`).
|
|
3
|
+
*
|
|
4
|
+
* The owner invariant is unchanged: per edited game there is ONE and only one
|
|
5
|
+
* browser tab. What changed on 2026-08-09 is how the server knows a tab
|
|
6
|
+
* exists. It used to know by socket: an identified connection meant "here", a
|
|
7
|
+
* close meant "lost — reopening". Both readings were wrong often enough to
|
|
8
|
+
* ship bugs — a blocked main thread reads as dead, a reload reads as a
|
|
9
|
+
* departure — and the relay compounded it by demanding an application-level
|
|
10
|
+
* state report before it would route a command at all. Now a tab proves
|
|
11
|
+
* itself by HEARTBEAT (a dedicated Worker, `tab-heartbeat.ts`), the table is
|
|
12
|
+
* the only truth, and this file is a thin shell:
|
|
13
|
+
*
|
|
14
|
+
* - Beats and socket events go IN as facts. Socket events are hints only:
|
|
15
|
+
* they update one boolean and schedule a reconcile. Nothing here opens,
|
|
16
|
+
* closes or blesses in a connect/disconnect handler.
|
|
17
|
+
* - ONE reconcile function runs on a tick and on those hints. It picks the
|
|
18
|
+
* single blessed tab (sticky while present, else oldest), tells extras to
|
|
19
|
+
* yield, and probes tabs whose beats have gapped. It NEVER opens one.
|
|
20
|
+
* - Everything it decides is journaled in tab vocabulary — appeared,
|
|
21
|
+
* heartbeat-gap, gap-closed, reloaded, departed, duplicated, blessed. The
|
|
22
|
+
* words "lost" and "reopening" are gone: neither was ever a fact about a
|
|
23
|
+
* tab.
|
|
24
|
+
*
|
|
25
|
+
* NOTHING IN THE BACKGROUND OPENS A TAB (owner, 2026-08-10). `vgai edit` opens
|
|
26
|
+
* one; that is the whole list. The reconciler used to open whenever the table
|
|
27
|
+
* went empty, on a budget a tab's own appearance RESET — so "open → tab
|
|
28
|
+
* appears → you close it → open" ran forever, and it spent a night reopening
|
|
29
|
+
* windows on two ports the owner was not using. The budget's doc comment
|
|
30
|
+
* already said a returning tab must never refill it; the refill at the other
|
|
31
|
+
* end of the file quietly re-enabled the loop it was written to stop. Closing
|
|
32
|
+
* a tab is an instruction, not a fault to repair.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { SessionJournalEvent } from './support/project/session-journal';
|
|
36
|
+
import {
|
|
37
|
+
DEFAULT_TAB_PRESENCE_CONFIG,
|
|
38
|
+
departedTabReport,
|
|
39
|
+
initialTabPresenceState,
|
|
40
|
+
latestCensus,
|
|
41
|
+
noteTabCommandOutcome,
|
|
42
|
+
presentTabs,
|
|
43
|
+
reconcile,
|
|
44
|
+
recordBeat,
|
|
45
|
+
recordTabClose,
|
|
46
|
+
setTabConnected,
|
|
47
|
+
setTabListener,
|
|
48
|
+
setTabRoute,
|
|
49
|
+
setTabSurface,
|
|
50
|
+
type TabBeat,
|
|
51
|
+
type TabCensus,
|
|
52
|
+
type TabCloseBeacon,
|
|
53
|
+
type TabPresenceConfig,
|
|
54
|
+
type TabPresenceReport,
|
|
55
|
+
type TabPresenceState,
|
|
56
|
+
type TabRecord,
|
|
57
|
+
type TabRoute,
|
|
58
|
+
type TabSurface,
|
|
59
|
+
tabArriving,
|
|
60
|
+
tabEndAcknowledged,
|
|
61
|
+
tabNeedsProbe,
|
|
62
|
+
tabPresenceReport,
|
|
63
|
+
tabState,
|
|
64
|
+
tabUnresponsive,
|
|
65
|
+
} from './tab-presence';
|
|
66
|
+
|
|
67
|
+
export type { TabRoute, TabSurface } from './tab-presence';
|
|
68
|
+
|
|
69
|
+
export type TabEnsureOutcome = 'focused' | 'adopt' | 'reloading' | 'arriving' | 'opening' | 'noop';
|
|
70
|
+
|
|
71
|
+
/** How long session end waits for the tabs to acknowledge it before going
|
|
72
|
+
* down anyway. A page that cannot answer must never hold a shutdown open. */
|
|
73
|
+
const SESSION_END_ACK_BUDGET_MS = 2_000;
|
|
74
|
+
|
|
75
|
+
/** How often the wait re-reads the table. The acks arrive over HTTP into this
|
|
76
|
+
* same process, so this is a poll of local state, not of a network. */
|
|
77
|
+
const SESSION_END_ACK_POLL_MS = 25;
|
|
78
|
+
|
|
79
|
+
export interface TabLifecycleController {
|
|
80
|
+
/**
|
|
81
|
+
* A control connection for this tab opened. A HINT — see the header.
|
|
82
|
+
*
|
|
83
|
+
* `surface` is what kind of PAGE opened it ({@link TabSurface}); it rides
|
|
84
|
+
* this hint because a connection is the first and only moment the server
|
|
85
|
+
* learns it, and it must be known for a page that never beats.
|
|
86
|
+
*/
|
|
87
|
+
onTabChannelOpen(tabId: string, surface?: TabSurface): void;
|
|
88
|
+
/** A control connection for this tab closed. A HINT. */
|
|
89
|
+
onTabChannelClose(tabId: string): void;
|
|
90
|
+
/** One heartbeat; `'re-mint'` tells a duplicated tab to take a new identity. */
|
|
91
|
+
onBeat(beat: TabBeat): 're-mint' | null;
|
|
92
|
+
/**
|
|
93
|
+
* A page said goodbye (`pagehide` → `navigator.sendBeacon`). The ONE page
|
|
94
|
+
* signal this file gained on 2026-09-17, and the only thing that tells a
|
|
95
|
+
* closed/reloading tab from a crashed one — see `tab-presence.ts`'s
|
|
96
|
+
* {@link TabCloseBeacon}.
|
|
97
|
+
*/
|
|
98
|
+
onTabClose(beacon: TabCloseBeacon): void;
|
|
99
|
+
/**
|
|
100
|
+
* How a relayed command against this tab ENDED. The server's own evidence
|
|
101
|
+
* for `hung`: a command that expired while the beats stayed fresh is the
|
|
102
|
+
* page not running, and the next answered one is proof it is.
|
|
103
|
+
*/
|
|
104
|
+
onCommandOutcome(tabId: string, outcome: 'timed-out' | 'answered'): void;
|
|
105
|
+
/**
|
|
106
|
+
* This tab's page reported its COMMAND LISTENER attached — the only
|
|
107
|
+
* evidence the server ever gets that the document behind the channel is
|
|
108
|
+
* actually running. A fact, like a beat: recorded, then reconciled on.
|
|
109
|
+
*/
|
|
110
|
+
onTabListener(tabId: string, pageEpoch: string): void;
|
|
111
|
+
/** The tab said which surface it is showing. */
|
|
112
|
+
onTabRoute(tabId: string, route: TabRoute): void;
|
|
113
|
+
/** Yield-page claim: bless the claimant's tab instead of the current one. */
|
|
114
|
+
claim(tabId?: string): void;
|
|
115
|
+
/** Converge for an edit/create invocation; opens the browser on 'opening'. */
|
|
116
|
+
ensure(open: boolean): TabEnsureOutcome;
|
|
117
|
+
/** Successor incoming on this port — do NOT close the tab at shutdown. */
|
|
118
|
+
expectRestart(): void;
|
|
119
|
+
/**
|
|
120
|
+
* `tab-close` to every tab, then WAIT for the tabs that were present to
|
|
121
|
+
* acknowledge it — see the implementation for what the wait is worth.
|
|
122
|
+
*/
|
|
123
|
+
notifySessionEnded(): Promise<void>;
|
|
124
|
+
/** One reconcile pass (interval-driven; exposed for tests). */
|
|
125
|
+
tick(): void;
|
|
126
|
+
stop(): void;
|
|
127
|
+
/** The one tab commands address, or null when none is present. */
|
|
128
|
+
blessedTabId(): string | null;
|
|
129
|
+
/** Present tabs, oldest first. */
|
|
130
|
+
present(): TabRecord[];
|
|
131
|
+
/** Present tabs whose PAGE never came up this page-load — no control channel
|
|
132
|
+
* at all, or a channel with no command listener behind it. See
|
|
133
|
+
* `tab-presence.ts`'s `tabUnresponsive` for the two stages and their budgets. */
|
|
134
|
+
unresponsive(): TabRecord[];
|
|
135
|
+
/** One row per present tab — what `vgai status` prints. */
|
|
136
|
+
report(): TabPresenceReport[];
|
|
137
|
+
/** One row per RECENTLY DEPARTED tab — the only place `closed` and
|
|
138
|
+
* `crashed` can be said, because both are verdicts about a tab that is no
|
|
139
|
+
* longer present. Bounded memory, not a log. */
|
|
140
|
+
departedReport(): TabPresenceReport[];
|
|
141
|
+
/** This tab's record, present or not (a refusal quotes it). */
|
|
142
|
+
tab(tabId: string): TabRecord | undefined;
|
|
143
|
+
/**
|
|
144
|
+
* This tab's newest resource census and how stale it is, on the lifecycle's
|
|
145
|
+
* OWN clock — the one that stamped it, so the age is a subtraction and not a
|
|
146
|
+
* guess across two clocks. Null when the tab is unknown or never sent one.
|
|
147
|
+
*
|
|
148
|
+
* Asked at exactly the moment a tab stops being normal (an abnormal socket
|
|
149
|
+
* close), which is why it does not require the tab to be present.
|
|
150
|
+
*/
|
|
151
|
+
censusOf(tabId: string): { census: TabCensus; ageMs: number } | null;
|
|
152
|
+
/** Read-only snapshot (tests/diagnostics). */
|
|
153
|
+
state(): Readonly<TabPresenceState>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface TabLifecycleOptions {
|
|
157
|
+
/** The URL the one tab must show (and the one auto-open opens). */
|
|
158
|
+
editorUrl: string;
|
|
159
|
+
/** Platform browser hand-off (same helper family the CLI uses). */
|
|
160
|
+
openUrl: (url: string) => void;
|
|
161
|
+
/** Targeted send to every live connection of one TAB; false when it has none. */
|
|
162
|
+
sendToTab: (tabId: string, event: string, data: unknown) => boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Send to EVERY connection in this lifecycle's scope. Session end is the
|
|
165
|
+
* only caller and the reason it exists: `tab-close` used to reach the
|
|
166
|
+
* blessed tab alone, so every OTHER connected tab (an extra on the yield
|
|
167
|
+
* page, a launcher tab, a second window) learned about the shutdown only by
|
|
168
|
+
* its socket dying and fell through to the generic "Editor disconnected"
|
|
169
|
+
* overlay. Returns how many connections took it.
|
|
170
|
+
*/
|
|
171
|
+
broadcastToClients: (event: string, data: unknown) => number;
|
|
172
|
+
/** Ask these tabs' workers to beat NOW (see `tab-heartbeat.ts`). */
|
|
173
|
+
probeTabs?: ((tabIds: readonly string[]) => void) | undefined;
|
|
174
|
+
/** Live view of the server's last index-page request stamp. */
|
|
175
|
+
lastIndexRequestAt: () => number | null;
|
|
176
|
+
/** One journal line per decision. */
|
|
177
|
+
journal?: ((event: SessionJournalEvent) => void) | undefined;
|
|
178
|
+
/** A tab's page reloaded under the same tabId (its `epochCount` advanced).
|
|
179
|
+
* The page that held everything addressed to the old page-load is gone;
|
|
180
|
+
* the control plane settles those commands on this. */
|
|
181
|
+
onTabReloaded?: ((tabId: string, epochCount: number) => void) | undefined;
|
|
182
|
+
now?: () => number;
|
|
183
|
+
config?: Partial<TabPresenceConfig>;
|
|
184
|
+
/** Reconcile cadence; 0 disables the interval (tests drive `tick()`). */
|
|
185
|
+
tickIntervalMs?: number;
|
|
186
|
+
/** False for a remote participant: enforce one tab, never open one here. */
|
|
187
|
+
maintain?: boolean;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function createTabLifecycle(options: TabLifecycleOptions): TabLifecycleController {
|
|
191
|
+
const config: TabPresenceConfig = { ...DEFAULT_TAB_PRESENCE_CONFIG, ...options.config };
|
|
192
|
+
const now = options.now ?? Date.now;
|
|
193
|
+
const journal = options.journal ?? ((): void => {});
|
|
194
|
+
const maintain = options.maintain ?? true;
|
|
195
|
+
let state = initialTabPresenceState();
|
|
196
|
+
let restartExpected = false;
|
|
197
|
+
/** Tabs already told to yield for the CURRENT blessing — one nudge each. */
|
|
198
|
+
let yielded = new Set<string>();
|
|
199
|
+
/** Page-loads already told to reload themselves — one attempt each, so a
|
|
200
|
+
* document that cannot boot is never put in a loop. */
|
|
201
|
+
const reloaded = new Set<string>();
|
|
202
|
+
|
|
203
|
+
function emit(events: readonly SessionJournalEvent[]): void {
|
|
204
|
+
for (const event of events) journal(event);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The single decision point. Every caller below either records a fact and
|
|
209
|
+
* lands here, or reads the table — nothing decides on its own.
|
|
210
|
+
*/
|
|
211
|
+
function run(): void {
|
|
212
|
+
const at = now();
|
|
213
|
+
const previousBlessed = state.blessedTabId;
|
|
214
|
+
const result = reconcile(
|
|
215
|
+
state,
|
|
216
|
+
{ now: at, lastIndexRequestAt: options.lastIndexRequestAt(), mayOpen: maintain },
|
|
217
|
+
config,
|
|
218
|
+
);
|
|
219
|
+
state = result.state;
|
|
220
|
+
emit(result.events);
|
|
221
|
+
|
|
222
|
+
if (result.blessedTabId !== previousBlessed) yielded = new Set();
|
|
223
|
+
for (const tabId of result.yieldTabIds) {
|
|
224
|
+
if (yielded.has(tabId)) continue;
|
|
225
|
+
// A worker beat can arrive before its page's control channel. Retry on
|
|
226
|
+
// the next reconcile when there was no channel to take this nudge.
|
|
227
|
+
if (!options.sendToTab(tabId, 'tab-yield', { reason: 'duplicate' })) continue;
|
|
228
|
+
yielded.add(tabId);
|
|
229
|
+
journal({ kind: 'tab-yielded', tabId8: tabId.slice(0, 8) });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Probing is a question the TABLE asks: only tabs whose beats have
|
|
233
|
+
// gapped, and only while they are still inside their grace. A hidden tab
|
|
234
|
+
// answers from its worker's message handler even when its timers are
|
|
235
|
+
// throttled, which is what keeps it present indefinitely.
|
|
236
|
+
if (options.probeTabs) {
|
|
237
|
+
const stale = [...state.tabs.values()]
|
|
238
|
+
.filter((tab) => tabNeedsProbe(tab, at, config))
|
|
239
|
+
.map((tab) => tab.tabId);
|
|
240
|
+
if (stale.length > 0) options.probeTabs(stale);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const intervalMs = options.tickIntervalMs ?? 1_000;
|
|
245
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
246
|
+
|
|
247
|
+
const controller: TabLifecycleController = {
|
|
248
|
+
onTabChannelOpen(tabId, surface) {
|
|
249
|
+
state = setTabConnected(state, tabId, true, now());
|
|
250
|
+
if (surface !== undefined) state = setTabSurface(state, tabId, surface);
|
|
251
|
+
run();
|
|
252
|
+
},
|
|
253
|
+
onTabChannelClose(tabId) {
|
|
254
|
+
state = setTabConnected(state, tabId, false, now());
|
|
255
|
+
run();
|
|
256
|
+
},
|
|
257
|
+
onBeat(beat) {
|
|
258
|
+
const outcome = recordBeat(state, beat, now(), config);
|
|
259
|
+
state = outcome.state;
|
|
260
|
+
emit(outcome.events);
|
|
261
|
+
for (const event of outcome.events)
|
|
262
|
+
if (event.kind === 'tab-reloaded') options.onTabReloaded?.(beat.tabId, event.epochCount);
|
|
263
|
+
// A beat that CLOSES a gap is a hint worth reconciling on immediately —
|
|
264
|
+
// it is the moment a tab stops being a question.
|
|
265
|
+
if (outcome.events.length > 0) run();
|
|
266
|
+
return outcome.reply;
|
|
267
|
+
},
|
|
268
|
+
onTabClose(beacon) {
|
|
269
|
+
const before = state;
|
|
270
|
+
state = recordTabClose(state, beacon, now());
|
|
271
|
+
// A goodbye is the moment a tab stops being a question — reconcile on it
|
|
272
|
+
// for the same reason a gap-closing beat does.
|
|
273
|
+
if (state !== before) run();
|
|
274
|
+
},
|
|
275
|
+
onCommandOutcome(tabId, outcome) {
|
|
276
|
+
state = noteTabCommandOutcome(state, tabId, now(), outcome);
|
|
277
|
+
},
|
|
278
|
+
onTabListener(tabId, pageEpoch) {
|
|
279
|
+
const before = state;
|
|
280
|
+
state = setTabListener(state, tabId, now(), pageEpoch);
|
|
281
|
+
// Only the transition is worth a reconcile: this arrives once per
|
|
282
|
+
// page-load, and it is the moment a tab stops being a question.
|
|
283
|
+
if (state !== before) run();
|
|
284
|
+
},
|
|
285
|
+
onTabRoute(tabId, route) {
|
|
286
|
+
state = setTabRoute(state, tabId, route);
|
|
287
|
+
},
|
|
288
|
+
claim(tabId) {
|
|
289
|
+
// "Use here instead": drop the blessing so the next reconcile picks the
|
|
290
|
+
// claimant. The claimant's own tab is preferred when it names itself.
|
|
291
|
+
const current = state.blessedTabId;
|
|
292
|
+
if (current !== null && current !== tabId) {
|
|
293
|
+
options.sendToTab(current, 'tab-yield', { reason: 'claimed' });
|
|
294
|
+
}
|
|
295
|
+
state = { ...state, blessedTabId: tabId ?? null, blessedAt: now() };
|
|
296
|
+
if (tabId !== undefined) {
|
|
297
|
+
journal({
|
|
298
|
+
kind: 'tab-blessed',
|
|
299
|
+
tabId8: tabId.slice(0, 8),
|
|
300
|
+
previousTabId8: current === null ? null : current.slice(0, 8),
|
|
301
|
+
reason: 'claimed',
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
yielded = new Set();
|
|
305
|
+
run();
|
|
306
|
+
},
|
|
307
|
+
ensure(open) {
|
|
308
|
+
run();
|
|
309
|
+
const at = now();
|
|
310
|
+
const blessed = state.blessedTabId;
|
|
311
|
+
// HEAL A DEAD DOCUMENT, and only when nothing can take a command.
|
|
312
|
+
//
|
|
313
|
+
// A page that connected and never attached a command listener cannot act
|
|
314
|
+
// on `tab-refocus` or `tab-adopt`: it buffers them for a consumer that is
|
|
315
|
+
// never coming (`index.html`'s bootstrap), so adopting it again leaves
|
|
316
|
+
// the session holding a tab where, as `vgai status` says, "no command
|
|
317
|
+
// will complete". `tab-reload` is the one message the bootstrap acts on
|
|
318
|
+
// without the module graph.
|
|
319
|
+
//
|
|
320
|
+
// The condition is about the SESSION, not about which tab is blessed. A
|
|
321
|
+
// dead tab that is merely present keeps `ensure` from opening a fresh one
|
|
322
|
+
// while never being able to run anything itself, and only checking the
|
|
323
|
+
// blessed tab left exactly that state unhealable. So: if NO tab has a
|
|
324
|
+
// command listener, reload every tab that has given up on getting one.
|
|
325
|
+
//
|
|
326
|
+
// And never while a healthy tab exists. Reloading a dead tab beside a
|
|
327
|
+
// working one produces two healthy tabs, and the bijection then tells one
|
|
328
|
+
// to yield — which closed the tab fourteen minutes into a replay and lost
|
|
329
|
+
// the run (measured 2026-09-15: `client-disconnected` code 1001, a
|
|
330
|
+
// deliberate unload, with the model's command still in flight).
|
|
331
|
+
//
|
|
332
|
+
// Once per page-load, and only from an explicit `vgai edit`, so a
|
|
333
|
+
// document that cannot boot is never put in a reload loop.
|
|
334
|
+
const anyHealthy = [...state.tabs.values()].some(
|
|
335
|
+
(tab) => tab.listenerThisEpoch && tab.connected,
|
|
336
|
+
);
|
|
337
|
+
if (!anyHealthy) {
|
|
338
|
+
for (const tab of state.tabs.values()) {
|
|
339
|
+
if (!tabUnresponsive(tab, at, config)) continue;
|
|
340
|
+
const epoch = tab.epoch ?? tab.tabId;
|
|
341
|
+
if (reloaded.has(epoch)) continue;
|
|
342
|
+
if (!options.sendToTab(tab.tabId, 'tab-reload', {})) continue;
|
|
343
|
+
reloaded.add(epoch);
|
|
344
|
+
// Deliberately UNJOURNALED. This used to write `tab-yielded`, which
|
|
345
|
+
// is a different decision with a different consequence — a yield
|
|
346
|
+
// tells a page to go away — and a reader diagnosing the wedge sees
|
|
347
|
+
// one word for two acts. The pair that already brackets this is
|
|
348
|
+
// honest and complete: `tab-unresponsive` (the cause, journaled by
|
|
349
|
+
// the reconciler) and `tab-reloaded` (the outcome, journaled when the
|
|
350
|
+
// successor page-load beats). A send that produced neither is a lost
|
|
351
|
+
// message, which is exactly what should read as nothing happening.
|
|
352
|
+
return 'reloading';
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// A BLESSED TAB THAT SAID GOODBYE IS NOT A TAB.
|
|
356
|
+
//
|
|
357
|
+
// Blessing is not released by a close: the record stays, `connected`
|
|
358
|
+
// goes false, and `listenerThisEpoch` is still latched from the epoch
|
|
359
|
+
// that just ended — so the branch below answered `focused` for a page
|
|
360
|
+
// that is gone, and the CLI printed "Editor already open on this
|
|
361
|
+
// project" and exited 0 having opened nothing. MEASURED twice on walk 5,
|
|
362
|
+
// each time by quitting the browser and running `vgai edit .`
|
|
363
|
+
// immediately: `vgai status` in the same breath read
|
|
364
|
+
//
|
|
365
|
+
// tab 7b08ccd5: CLOSED 7.4s — its page sent a close beacon 7.4s ago
|
|
366
|
+
// and no new page-load is beating
|
|
367
|
+
//
|
|
368
|
+
// and `vgai edit` said the editor was already open. A second run ~30 s
|
|
369
|
+
// later, once the record had aged out of `presentTabs`, opened the tab
|
|
370
|
+
// normally — so the defect was a WINDOW, which is exactly the window a
|
|
371
|
+
// person or an agent reaches for `vgai edit` in.
|
|
372
|
+
//
|
|
373
|
+
// The verdict this asks is the SAME ONE the status line prints
|
|
374
|
+
// (`tabState`), so the two can no longer disagree about whether there is
|
|
375
|
+
// a tab. A `reloading` verdict is deliberately not here: that page said
|
|
376
|
+
// goodbye and a successor is already beating, which the branches below
|
|
377
|
+
// handle as the arriving tab it is.
|
|
378
|
+
const saidGoodbye = (tab: TabRecord): boolean => {
|
|
379
|
+
const verdict = tabState(tab, at, config).state;
|
|
380
|
+
return verdict === 'closed' || verdict === 'ended';
|
|
381
|
+
};
|
|
382
|
+
const blessedRecord = blessed === null ? undefined : state.tabs.get(blessed);
|
|
383
|
+
if (blessed !== null && !(blessedRecord !== undefined && saidGoodbye(blessedRecord))) {
|
|
384
|
+
const record = blessedRecord;
|
|
385
|
+
if (record?.route === 'no-project') {
|
|
386
|
+
// The tab is live but on the launcher: retarget THAT tab instead of
|
|
387
|
+
// opening one beside it.
|
|
388
|
+
options.sendToTab(blessed, 'tab-adopt', { url: options.editorUrl });
|
|
389
|
+
return 'adopt';
|
|
390
|
+
}
|
|
391
|
+
// A PAGE THAT HAS NOT ATTACHED A COMMAND LISTENER THIS EPOCH IS NOT
|
|
392
|
+
// `focused`. `focused` is this endpoint's word for "converged — a tab
|
|
393
|
+
// is on this session and it answers", and the CLI reads it as exactly
|
|
394
|
+
// that (`tab-attach.ts`'s `planTabAttach`: `focused` -> `attached` ->
|
|
395
|
+
// "Editor already open on this project", exit 0).
|
|
396
|
+
//
|
|
397
|
+
// MEASURED 2026-09-19 against a page stalled before React mounts (the
|
|
398
|
+
// wedge in WORK.md §"The editor tab can wedge in a state `vgai edit`
|
|
399
|
+
// cannot self-heal"): for the WHOLE `listenerBudgetMs` — two minutes —
|
|
400
|
+
// this branch answered
|
|
401
|
+
//
|
|
402
|
+
// POST /__editor/tab/ensure -> {"status":"focused"}
|
|
403
|
+
// tab=025db401 connected=true channelThisEpoch=true
|
|
404
|
+
// listenerThisEpoch=false route=unknown epochAge=95349ms
|
|
405
|
+
//
|
|
406
|
+
// for a tab that could never run a command, and `tab-refocus` went to
|
|
407
|
+
// a page that buffers it for a consumer that is never coming (see the
|
|
408
|
+
// heal note above). That is the window the blind contributor probe hit
|
|
409
|
+
// three times in one session.
|
|
410
|
+
//
|
|
411
|
+
// `arriving` is the truthful word for it: the page-load is on its way
|
|
412
|
+
// in and the caller must WAIT for the document rather than be told it
|
|
413
|
+
// already has one. Past the budget the heal above turns it into
|
|
414
|
+
// `reloading`; both map to `wait-arriving`, and the CLI's wait now
|
|
415
|
+
// keys on the listener (`editor-sessions.ts`), so a page that never
|
|
416
|
+
// comes up ends in a named, non-zero failure instead of a lie.
|
|
417
|
+
if (record !== undefined && !record.listenerThisEpoch) return 'arriving';
|
|
418
|
+
options.sendToTab(blessed, 'tab-refocus', { url: options.editorUrl });
|
|
419
|
+
return 'focused';
|
|
420
|
+
}
|
|
421
|
+
if (tabArriving(state, at, options.lastIndexRequestAt(), config)) return 'arriving';
|
|
422
|
+
// A PRESENT TAB IS A TAB, blessed or not. Blessing is released the moment
|
|
423
|
+
// a page stops being eligible (`tab-presence.ts`'s `partitionEligible`),
|
|
424
|
+
// and reading "nothing blessed" as "no tab" is the same mistake the yield
|
|
425
|
+
// filter made — there it closed the session's only tab, here it would
|
|
426
|
+
// open a second one on top of it, which is the duplicate the bijection
|
|
427
|
+
// exists to forbid. The heal above is the door for a present tab that
|
|
428
|
+
// cannot run; waiting is the answer once it has been used.
|
|
429
|
+
// …and a tab that said goodbye is not present either, for the reason the
|
|
430
|
+
// blessed branch above states: `tabPresent` keeps a closed tab for its
|
|
431
|
+
// whole grace, which is the same window that made `vgai edit` report a
|
|
432
|
+
// tab it did not have.
|
|
433
|
+
if (presentTabs(state, at, config).some((tab) => !saidGoodbye(tab))) return 'arriving';
|
|
434
|
+
if (!open || !maintain) return 'noop';
|
|
435
|
+
// An explicit `vgai edit` is the ONLY thing that opens a tab. Nothing in
|
|
436
|
+
// the reconcile loop opens one: a tab you closed stays closed.
|
|
437
|
+
options.openUrl(options.editorUrl);
|
|
438
|
+
return 'opening';
|
|
439
|
+
},
|
|
440
|
+
expectRestart() {
|
|
441
|
+
restartExpected = true;
|
|
442
|
+
},
|
|
443
|
+
/**
|
|
444
|
+
* THE END OF THE SESSION IS A FACT THE PAGE HAS TO CONFIRM.
|
|
445
|
+
*
|
|
446
|
+
* This used to broadcast `tab-close`, sleep 150 ms blind and return, and
|
|
447
|
+
* the caller then tore the HTTP server down (`dev.ts`). Nothing waited for
|
|
448
|
+
* the message and nothing recorded whether it landed — and everything the
|
|
449
|
+
* page tears down on the session's end hangs off it: `tab-close` →
|
|
450
|
+
* `markSessionEnded` → every `editorHost().session.onEnded` terminator,
|
|
451
|
+
* `terminateBlenderRuntime` among them. Measured 2026-09-19/20: when the
|
|
452
|
+
* socket died before the event did, the page instead fell to the lease
|
|
453
|
+
* guard's `server-gone` path — main-thread timers Chrome throttles to once
|
|
454
|
+
* a minute in a long-hidden tab — and the tab's Blender engine worker (one
|
|
455
|
+
* engine thread plus a 16-thread pthread pool) stayed at 100%+ CPU for
|
|
456
|
+
* minutes after `vgai close`, with the box swapping.
|
|
457
|
+
*
|
|
458
|
+
* So the sleep is gone and a real wait takes its place: every tab PRESENT
|
|
459
|
+
* when the end was announced has to send a `session-ended` goodbye
|
|
460
|
+
* (`src/tab-lifecycle-client.ts`, the same beacon route `pagehide` uses),
|
|
461
|
+
* bounded at {@link SESSION_END_ACK_BUDGET_MS}. The bound is the point —
|
|
462
|
+
* a page that cannot answer must not hold the shutdown open — and the
|
|
463
|
+
* journal says which of the two happened, so `vgai close` can print it
|
|
464
|
+
* instead of the operator having to find the CPU themselves.
|
|
465
|
+
*/
|
|
466
|
+
async notifySessionEnded() {
|
|
467
|
+
controller.stop();
|
|
468
|
+
if (restartExpected) return;
|
|
469
|
+
const awaiting = presentTabs(state, now(), config).map((tab) => tab.tabId);
|
|
470
|
+
const startedAt = now();
|
|
471
|
+
const unacked = (): string[] => awaiting.filter((id) => !tabEndAcknowledged(state, id));
|
|
472
|
+
const journalUnacked = (missing: readonly string[]): void => {
|
|
473
|
+
journal({
|
|
474
|
+
kind: 'session-end-unacked',
|
|
475
|
+
tabId8s: missing.map((id) => id.slice(0, 8)),
|
|
476
|
+
ms: now() - startedAt,
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
if (options.broadcastToClients('tab-close', {}) === 0) {
|
|
480
|
+
// Nobody took the message, so no ack can ever come — waiting the
|
|
481
|
+
// budget out would only delay the exit. Say so at once when there was
|
|
482
|
+
// a tab to tell; a session with no tabs has nothing to record.
|
|
483
|
+
const missing = unacked();
|
|
484
|
+
if (missing.length > 0) journalUnacked(missing);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
for (;;) {
|
|
488
|
+
const missing = unacked();
|
|
489
|
+
if (missing.length === 0) {
|
|
490
|
+
journal({
|
|
491
|
+
kind: 'session-end-acked',
|
|
492
|
+
tabs: awaiting.length,
|
|
493
|
+
ms: now() - startedAt,
|
|
494
|
+
});
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (now() - startedAt >= SESSION_END_ACK_BUDGET_MS) {
|
|
498
|
+
journalUnacked(missing);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
await new Promise((resolve) => setTimeout(resolve, SESSION_END_ACK_POLL_MS));
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
tick() {
|
|
505
|
+
run();
|
|
506
|
+
},
|
|
507
|
+
stop() {
|
|
508
|
+
if (timer !== null) {
|
|
509
|
+
clearInterval(timer);
|
|
510
|
+
timer = null;
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
blessedTabId() {
|
|
514
|
+
return state.blessedTabId;
|
|
515
|
+
},
|
|
516
|
+
present() {
|
|
517
|
+
return presentTabs(state, now(), config);
|
|
518
|
+
},
|
|
519
|
+
unresponsive() {
|
|
520
|
+
const at = now();
|
|
521
|
+
return presentTabs(state, at, config).filter((tab) => tabUnresponsive(tab, at, config));
|
|
522
|
+
},
|
|
523
|
+
report() {
|
|
524
|
+
return tabPresenceReport(state, now(), config);
|
|
525
|
+
},
|
|
526
|
+
departedReport() {
|
|
527
|
+
return departedTabReport(state, now(), config);
|
|
528
|
+
},
|
|
529
|
+
tab(tabId) {
|
|
530
|
+
return state.tabs.get(tabId);
|
|
531
|
+
},
|
|
532
|
+
censusOf(tabId) {
|
|
533
|
+
const record = state.tabs.get(tabId);
|
|
534
|
+
const newest = record === undefined ? null : latestCensus(record);
|
|
535
|
+
if (newest === null) return null;
|
|
536
|
+
const { at, ...census } = newest;
|
|
537
|
+
return { census, ageMs: now() - at };
|
|
538
|
+
},
|
|
539
|
+
state() {
|
|
540
|
+
return state;
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
if (intervalMs > 0) {
|
|
545
|
+
timer = setInterval(() => controller.tick(), intervalMs);
|
|
546
|
+
timer.unref?.();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return controller;
|
|
550
|
+
}
|