@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,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared event fan-out for editor tabs — ONE message vocabulary over TWO
|
|
3
|
+
* transports.
|
|
4
|
+
*
|
|
5
|
+
* A connected editor page is a `EditorEventClient` in this pool, and every
|
|
6
|
+
* caller (asset browser, Colyseus plugin, the command relay, the tab
|
|
7
|
+
* bijection) addresses it the same way regardless of how it got here:
|
|
8
|
+
*
|
|
9
|
+
* - `ServerResponse` — the classic `GET /__editor/events` SSE stream. Still
|
|
10
|
+
* the transport the share tunnel bridges through (TryCloudflare cannot do
|
|
11
|
+
* SSE to the browser, so its gateway consumes this and re-frames it), and
|
|
12
|
+
* still curl-debuggable.
|
|
13
|
+
* - `EditorSocketClient` — a WebSocket upgrade on that SAME path. Local tabs
|
|
14
|
+
* use this one: it carries the identical named events downstream AND the
|
|
15
|
+
* tab's control replies upstream, so receipts/results/state no longer
|
|
16
|
+
* compete with module loads for the browser's per-origin HTTP connection
|
|
17
|
+
* pool. It also answers protocol pings from the browser's network stack
|
|
18
|
+
* while the main thread is blocked, which is what lets the relay say
|
|
19
|
+
* "busy" instead of guessing "dead".
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ServerResponse } from 'node:http';
|
|
23
|
+
import type { CommandListenerFacts } from './server-utils';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* What a tab last told the server about itself (`POST /__editor/state` or the
|
|
27
|
+
* equivalent control frame).
|
|
28
|
+
*
|
|
29
|
+
* It is EVIDENCE FOR A REFUSAL and nothing else now. It used to elect the tab
|
|
30
|
+
* that owned commands, and that election is what the tab table replaced: who
|
|
31
|
+
* owns the session is a presence question (`server/tab-presence.ts`), and
|
|
32
|
+
* answering it from a report the page is not obliged to send is how a live,
|
|
33
|
+
* blessed tab became unroutable and its commands came back "disconnected".
|
|
34
|
+
* What survives is the part that was always true — when a command goes
|
|
35
|
+
* unacknowledged, the refusal can say what that tab was showing and how long
|
|
36
|
+
* it has been quiet instead of guessing.
|
|
37
|
+
*/
|
|
38
|
+
export interface ClientControlHealth {
|
|
39
|
+
/** `healthy` means the tab explicitly reported an empty pageErrors list. */
|
|
40
|
+
readonly errorState: 'healthy' | 'unknown' | 'errored';
|
|
41
|
+
readonly visible: boolean;
|
|
42
|
+
readonly focused: boolean;
|
|
43
|
+
readonly updatedAt: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A duplex control socket in the pool.
|
|
48
|
+
*
|
|
49
|
+
* Beyond delivery it exposes the two liveness facts an SSE response cannot:
|
|
50
|
+
* the age of the last protocol pong (answered by the browser's NETWORK stack,
|
|
51
|
+
* so it stays fresh through a blocked main thread) and a main-thread echo
|
|
52
|
+
* round trip (answered by the inline bootstrap in index.html, so an
|
|
53
|
+
* unanswered echo can only mean a blocked main thread).
|
|
54
|
+
*/
|
|
55
|
+
export interface EditorSocketClient {
|
|
56
|
+
/** Structural discriminator against `ServerResponse`. */
|
|
57
|
+
readonly kind: 'socket';
|
|
58
|
+
/** Deliver one framed event; false when the socket can no longer take it. */
|
|
59
|
+
sendFrame(event: string, data: string, id?: string): boolean;
|
|
60
|
+
/** True once the socket is closing or closed. */
|
|
61
|
+
isClosed(): boolean;
|
|
62
|
+
/** Age in ms of the most recent protocol pong, or null before the first. */
|
|
63
|
+
lastPongAgeMs(): number | null;
|
|
64
|
+
/** Round-trip the page's inline main-thread echo responder. */
|
|
65
|
+
echo(timeoutMs: number): Promise<boolean>;
|
|
66
|
+
/** End this exact connection because a newer generation superseded it. */
|
|
67
|
+
close(code: number, reason: string): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Either transport, as held by the pool and by a pending command's owner. */
|
|
71
|
+
export type EditorEventClient = ServerResponse | EditorSocketClient;
|
|
72
|
+
|
|
73
|
+
const sseClients = new Set<EditorEventClient>();
|
|
74
|
+
const clientIds = new Map<EditorEventClient, string>();
|
|
75
|
+
const clientParticipantIds = new Map<EditorEventClient, string>();
|
|
76
|
+
const clientControlHealth = new Map<string, ClientControlHealth>();
|
|
77
|
+
/**
|
|
78
|
+
* Per-page-load command-listener facts (`server-utils.ts`'s
|
|
79
|
+
* `CommandListenerFacts`), keyed by client id — which IS the page-load id, so
|
|
80
|
+
* a reload starts from nothing rather than inheriting the dead page's verdict.
|
|
81
|
+
*
|
|
82
|
+
* OWNERSHIP: this map is written only by the three `note*` helpers below, and
|
|
83
|
+
* an entry is dropped only by `dropClient`, exactly where `clientControlHealth`
|
|
84
|
+
* beside it is dropped — one lifetime, one teardown path, no timer.
|
|
85
|
+
*/
|
|
86
|
+
const clientListenerFacts = new Map<string, MutableCommandListenerFacts>();
|
|
87
|
+
|
|
88
|
+
interface MutableCommandListenerFacts {
|
|
89
|
+
attachedAt: number | null;
|
|
90
|
+
lastRelayAt: number | null;
|
|
91
|
+
lastReceiptAt: number | null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function factsFor(clientId: string): MutableCommandListenerFacts {
|
|
95
|
+
const existing = clientListenerFacts.get(clientId);
|
|
96
|
+
if (existing) return existing;
|
|
97
|
+
const fresh: MutableCommandListenerFacts = {
|
|
98
|
+
attachedAt: null,
|
|
99
|
+
lastRelayAt: null,
|
|
100
|
+
lastReceiptAt: null,
|
|
101
|
+
};
|
|
102
|
+
clientListenerFacts.set(clientId, fresh);
|
|
103
|
+
return fresh;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const KEEPALIVE_MS = 15_000;
|
|
107
|
+
let keepaliveTimer: ReturnType<typeof setInterval> | null = null;
|
|
108
|
+
|
|
109
|
+
/** True for the WebSocket transport. */
|
|
110
|
+
export function isEditorSocketClient(client: EditorEventClient): client is EditorSocketClient {
|
|
111
|
+
return (client as Partial<EditorSocketClient>).kind === 'socket';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether this client's transport can still carry an event.
|
|
116
|
+
*
|
|
117
|
+
* Both flags are needed on the SSE side: `writableEnded` reports that the
|
|
118
|
+
* SERVER called `res.end()`, which nothing does for an SSE response, so a
|
|
119
|
+
* client abort leaves `writableEnded: false, destroyed: true` (measured).
|
|
120
|
+
*/
|
|
121
|
+
export function isClientAlive(client: EditorEventClient): boolean {
|
|
122
|
+
if (isEditorSocketClient(client)) return !client.isClosed();
|
|
123
|
+
return !client.writableEnded && !client.destroyed;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** End one exact event-stream generation. Its ordinary close handler owns all
|
|
127
|
+
* table and pending-command cleanup. */
|
|
128
|
+
export function closeClient(client: EditorEventClient, code: number, reason: string): void {
|
|
129
|
+
if (isEditorSocketClient(client)) {
|
|
130
|
+
client.close(code, reason);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
client.end();
|
|
135
|
+
} catch {
|
|
136
|
+
/* The stream is already ending; its request close still owns cleanup. */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function dropClient(client: EditorEventClient): void {
|
|
141
|
+
const clientId = clientIds.get(client);
|
|
142
|
+
sseClients.delete(client);
|
|
143
|
+
clientIds.delete(client);
|
|
144
|
+
clientParticipantIds.delete(client);
|
|
145
|
+
if (clientId && ![...clientIds.values()].includes(clientId)) {
|
|
146
|
+
clientControlHealth.delete(clientId);
|
|
147
|
+
clientListenerFacts.delete(clientId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Deliver one named event, removing the client from the pool if it is dead. */
|
|
152
|
+
function deliver(client: EditorEventClient, event: string, data: string, id?: string): boolean {
|
|
153
|
+
try {
|
|
154
|
+
if (isEditorSocketClient(client)) {
|
|
155
|
+
if (client.isClosed()) {
|
|
156
|
+
dropClient(client);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
if (!client.sendFrame(event, data, id)) {
|
|
160
|
+
dropClient(client);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
// `writableEnded` guards against writing to a closed stream.
|
|
166
|
+
if (client.writableEnded) {
|
|
167
|
+
dropClient(client);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
client.write(`${id === undefined ? '' : `id: ${id}\n`}event: ${event}\ndata: ${data}\n\n`);
|
|
171
|
+
return true;
|
|
172
|
+
} catch {
|
|
173
|
+
// SC3: one dead client must not abort the broadcast loop.
|
|
174
|
+
dropClient(client);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Start the keepalive ticker once at least one client is connected. */
|
|
180
|
+
function ensureKeepalive(): void {
|
|
181
|
+
if (keepaliveTimer || sseClients.size === 0) return;
|
|
182
|
+
keepaliveTimer = setInterval(() => {
|
|
183
|
+
if (sseClients.size === 0) {
|
|
184
|
+
stopKeepalive();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// Socket clients have the protocol ping instead (editor-control-socket.ts).
|
|
188
|
+
for (const client of [...sseClients]) {
|
|
189
|
+
if (isEditorSocketClient(client)) continue;
|
|
190
|
+
try {
|
|
191
|
+
if (client.writableEnded) dropClient(client);
|
|
192
|
+
else client.write(':keepalive\n\n');
|
|
193
|
+
} catch {
|
|
194
|
+
dropClient(client);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}, KEEPALIVE_MS);
|
|
198
|
+
// Don't keep the process alive solely for the heartbeat.
|
|
199
|
+
keepaliveTimer.unref?.();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function stopKeepalive(): void {
|
|
203
|
+
if (keepaliveTimer) {
|
|
204
|
+
clearInterval(keepaliveTimer);
|
|
205
|
+
keepaliveTimer = null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Send an event to all connected editor clients. */
|
|
210
|
+
export function broadcast(event: string, data: unknown): void {
|
|
211
|
+
const sequence =
|
|
212
|
+
data &&
|
|
213
|
+
typeof data === 'object' &&
|
|
214
|
+
Number.isSafeInteger((data as { sequence?: unknown }).sequence)
|
|
215
|
+
? Number((data as { sequence: number }).sequence)
|
|
216
|
+
: null;
|
|
217
|
+
const payload = JSON.stringify(data);
|
|
218
|
+
// Snapshot so deletions during iteration are safe.
|
|
219
|
+
for (const client of [...sseClients]) {
|
|
220
|
+
deliver(client, event, payload, sequence === null ? undefined : String(sequence));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Send an event to the connection(s) carrying one specific clientId
|
|
226
|
+
* (tab-bijection lifecycle: yield/refocus/close target exactly one tab).
|
|
227
|
+
* Returns true when at least one live connection received it.
|
|
228
|
+
*/
|
|
229
|
+
export function sendToClient(clientId: string, event: string, data: unknown): boolean {
|
|
230
|
+
const payload = JSON.stringify(data);
|
|
231
|
+
let delivered = false;
|
|
232
|
+
for (const [client, id] of [...clientIds]) {
|
|
233
|
+
if (id !== clientId) continue;
|
|
234
|
+
if (deliver(client, event, payload)) delivered = true;
|
|
235
|
+
}
|
|
236
|
+
return delivered;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Send an event to the FIRST live connection carrying this clientId, and hand
|
|
241
|
+
* back the handle it went to.
|
|
242
|
+
*
|
|
243
|
+
* The command relay needs the handle, not a boolean: a command is owned by
|
|
244
|
+
* the connection that took it, so the disconnect settlement can find it
|
|
245
|
+
* again. `sendToClient` fans out to every connection of a client and can only
|
|
246
|
+
* answer "did anyone take it", which is the right shape for a lifecycle
|
|
247
|
+
* notification and the wrong one for a side effect that must happen once.
|
|
248
|
+
*/
|
|
249
|
+
export function sendToClientHandle(
|
|
250
|
+
clientId: string,
|
|
251
|
+
event: string,
|
|
252
|
+
data: unknown,
|
|
253
|
+
): EditorEventClient | null {
|
|
254
|
+
const payload = JSON.stringify(data);
|
|
255
|
+
for (const [client, id] of [...clientIds]) {
|
|
256
|
+
if (id !== clientId) continue;
|
|
257
|
+
if (deliver(client, event, payload)) return client;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Send an event to EVERY live connection, optionally scoped to one
|
|
264
|
+
* participant. Distinct from `broadcast` only in the scoping — session end is
|
|
265
|
+
* the caller: `tab-close` has to reach the extras and the launcher tabs too,
|
|
266
|
+
* not just the blessed one, or every other tab falls through to the generic
|
|
267
|
+
* "Editor disconnected" overlay when the process exits under it.
|
|
268
|
+
*/
|
|
269
|
+
export function sendToAllClients(event: string, data: unknown, participantId?: string): number {
|
|
270
|
+
const payload = JSON.stringify(data);
|
|
271
|
+
let delivered = 0;
|
|
272
|
+
for (const client of [...sseClients]) {
|
|
273
|
+
if (participantId && clientParticipantIds.get(client) !== participantId) continue;
|
|
274
|
+
if (deliver(client, event, payload)) delivered++;
|
|
275
|
+
}
|
|
276
|
+
return delivered;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Add a client connection. Call from the /__editor/events handler. */
|
|
280
|
+
export function addClient(res: EditorEventClient, clientId?: string, participantId?: string): void {
|
|
281
|
+
sseClients.add(res);
|
|
282
|
+
if (clientId) clientIds.set(res, clientId);
|
|
283
|
+
if (participantId) clientParticipantIds.set(res, participantId);
|
|
284
|
+
ensureKeepalive();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Remove a client connection. Call on request/socket close. */
|
|
288
|
+
export function removeClient(res: EditorEventClient): void {
|
|
289
|
+
dropClient(res);
|
|
290
|
+
if (sseClients.size === 0) stopKeepalive();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Record what a tab last said about itself — read only by a refusal. */
|
|
294
|
+
export function updateClientControlHealth(clientId: string, health: ClientControlHealth): void {
|
|
295
|
+
clientControlHealth.set(clientId, health);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Clear project-specific health snapshots while keeping live sockets. */
|
|
299
|
+
export function clearClientControlHealth(): void {
|
|
300
|
+
clientControlHealth.clear();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** The page said its command listener attached (a timestamp) or detached
|
|
304
|
+
* (`null`). See `src/editor-api.ts`'s `reportCommandListener`. */
|
|
305
|
+
export function noteCommandListenerAttached(clientId: string, at: number | null): void {
|
|
306
|
+
factsFor(clientId).attachedAt = at;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** The relay handed this page a command. */
|
|
310
|
+
export function noteCommandRelay(clientId: string, at: number = Date.now()): void {
|
|
311
|
+
factsFor(clientId).lastRelayAt = at;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** This page acknowledged receipt of a relayed command — the OTHER half of the
|
|
315
|
+
* verdict, and the one that needs no cooperation beyond the receipt the relay
|
|
316
|
+
* already depends on. */
|
|
317
|
+
export function noteCommandReceipt(clientId: string | undefined, at: number = Date.now()): void {
|
|
318
|
+
if (clientId === undefined) return;
|
|
319
|
+
factsFor(clientId).lastReceiptAt = at;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** What is MEASURED about this page-load's command listener. A page-load that
|
|
323
|
+
* has never reported anything reads as all-null, which
|
|
324
|
+
* `commandListenerHealth` states as `'not attached'` — never as healthy. */
|
|
325
|
+
export function commandListenerFactsFor(clientId: string): CommandListenerFacts {
|
|
326
|
+
return (
|
|
327
|
+
clientListenerFacts.get(clientId) ?? {
|
|
328
|
+
attachedAt: null,
|
|
329
|
+
lastRelayAt: null,
|
|
330
|
+
lastReceiptAt: null,
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Number of currently-connected clients (exposed for tests). */
|
|
336
|
+
export function clientCount(participantId?: string): number {
|
|
337
|
+
if (!participantId) return sseClients.size;
|
|
338
|
+
return [...clientParticipantIds.values()].filter((candidate) => candidate === participantId)
|
|
339
|
+
.length;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** The last health a given tab reported, or `undefined` if it never has. Read
|
|
343
|
+
* when a relayed command goes unacknowledged, so the refusal can name what
|
|
344
|
+
* that tab was showing and how long it has been silent rather than guessing. */
|
|
345
|
+
export function clientControlHealthFor(clientId: string | null): ClientControlHealth | undefined {
|
|
346
|
+
return clientId === null ? undefined : clientControlHealth.get(clientId);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Client identity for a handle returned by `sendToClientHandle`. */
|
|
350
|
+
export function clientIdForResponse(response: EditorEventClient): string | null {
|
|
351
|
+
return clientIds.get(response) ?? null;
|
|
352
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What code is this editor session actually serving?
|
|
3
|
+
*
|
|
4
|
+
* Measured defect (2026-08-09): a user's editor server came from an engine
|
|
5
|
+
* checkout parked on a dead feature branch, 162 commits behind `origin/main`.
|
|
6
|
+
* Three fixes that had already merged were simply absent from the running
|
|
7
|
+
* editor, and hours went into debugging their absence as live bugs — because
|
|
8
|
+
* NOTHING in the product reported which commit was being served. The URL, the
|
|
9
|
+
* project name, the session id and the engine's npm version were all correct
|
|
10
|
+
* and all useless for that question.
|
|
11
|
+
*
|
|
12
|
+
* So the server computes its own checkout's git provenance and reports it
|
|
13
|
+
* everywhere a human or an agent looks: the `/__editor/project` payload, the
|
|
14
|
+
* server's own startup banner, `vgai edit`'s ready line, and `vgai status`.
|
|
15
|
+
*
|
|
16
|
+
* Two rules this module exists to hold:
|
|
17
|
+
*
|
|
18
|
+
* - **Report, never refuse.** A stale checkout still serves. Nothing here
|
|
19
|
+
* gates, delays, or fails a boot; every field degrades to `null` on any
|
|
20
|
+
* error (no git, not a repo, no `origin/main` ref) and the line still
|
|
21
|
+
* prints.
|
|
22
|
+
* - **Never touch the network.** `behindOriginMain` is measured against the
|
|
23
|
+
* LOCAL `origin/main` ref only. A fetch here would make every editor boot
|
|
24
|
+
* depend on a remote being reachable, which is exactly the kind of
|
|
25
|
+
* always-on dependency a diagnostic must not introduce. The number is
|
|
26
|
+
* therefore "behind the last main you fetched", and that is still the
|
|
27
|
+
* number that would have caught the 162-commit case.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { execFile } from 'node:child_process';
|
|
31
|
+
import { realpathSync } from 'node:fs';
|
|
32
|
+
import { resolve } from 'node:path';
|
|
33
|
+
import { promisify } from 'node:util';
|
|
34
|
+
|
|
35
|
+
const execFileAsync = promisify(execFile);
|
|
36
|
+
|
|
37
|
+
export interface EngineProvenance {
|
|
38
|
+
/** Current branch, or `null` when detached / unknown. */
|
|
39
|
+
branch: string | null;
|
|
40
|
+
/** Short HEAD commit, or `null` when unknown. */
|
|
41
|
+
commit: string | null;
|
|
42
|
+
/** Commits on the LOCAL `origin/main` ref that HEAD does not have. */
|
|
43
|
+
behindOriginMain: number | null;
|
|
44
|
+
/** Tracked-file modifications in the working tree. */
|
|
45
|
+
dirty: boolean | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const UNKNOWN_ENGINE_PROVENANCE: EngineProvenance = {
|
|
49
|
+
branch: null,
|
|
50
|
+
commit: null,
|
|
51
|
+
behindOriginMain: null,
|
|
52
|
+
dirty: null,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Runs `git <args>` in the engine checkout. Returns trimmed stdout on success
|
|
57
|
+
* — `''` is a legitimate success (a clean `git status --porcelain`) and must
|
|
58
|
+
* stay distinguishable from failure, which is `null`.
|
|
59
|
+
*/
|
|
60
|
+
export type GitRunner = (args: readonly string[]) => Promise<string | null>;
|
|
61
|
+
|
|
62
|
+
export function createGitRunner(cwd: string): GitRunner {
|
|
63
|
+
return async (args) => {
|
|
64
|
+
try {
|
|
65
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
66
|
+
cwd,
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
timeout: 5_000,
|
|
69
|
+
maxBuffer: 1024 * 1024,
|
|
70
|
+
});
|
|
71
|
+
return stdout.trim();
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function canonical(path: string): string {
|
|
79
|
+
try {
|
|
80
|
+
return realpathSync(path);
|
|
81
|
+
} catch {
|
|
82
|
+
return resolve(path);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* `engineRoot` must BE the repository git discovers, not merely live inside
|
|
88
|
+
* one. A registry-installed editor sits at `<game>/node_modules/@vgai/editor`,
|
|
89
|
+
* where `rev-parse --show-toplevel` happily answers with the GAME's repo — and
|
|
90
|
+
* reporting the game's branch under the word "engine" is worse than reporting
|
|
91
|
+
* nothing. Mismatch therefore degrades to all-null, the same as no git at all.
|
|
92
|
+
*/
|
|
93
|
+
export async function computeEngineProvenance(
|
|
94
|
+
engineRoot: string,
|
|
95
|
+
run: GitRunner,
|
|
96
|
+
): Promise<EngineProvenance> {
|
|
97
|
+
const toplevel = await run(['rev-parse', '--show-toplevel']);
|
|
98
|
+
if (toplevel === null || toplevel === '') return UNKNOWN_ENGINE_PROVENANCE;
|
|
99
|
+
if (canonical(toplevel) !== canonical(engineRoot)) return UNKNOWN_ENGINE_PROVENANCE;
|
|
100
|
+
|
|
101
|
+
const [rawBranch, commit, rawBehind, rawStatus] = await Promise.all([
|
|
102
|
+
run(['rev-parse', '--abbrev-ref', 'HEAD']),
|
|
103
|
+
run(['rev-parse', '--short', 'HEAD']),
|
|
104
|
+
run(['rev-list', '--count', 'HEAD..origin/main']),
|
|
105
|
+
run(['status', '--porcelain', '--untracked-files=no']),
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
// Detached HEAD answers the literal string `HEAD`, which would print as
|
|
109
|
+
// `engine: HEAD @ abc1234`. That is a branch name nobody has.
|
|
110
|
+
const branch = rawBranch === null || rawBranch === '' || rawBranch === 'HEAD' ? null : rawBranch;
|
|
111
|
+
const behind = rawBehind === null ? Number.NaN : Number.parseInt(rawBehind, 10);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
branch,
|
|
115
|
+
commit: commit === null || commit === '' ? null : commit,
|
|
116
|
+
behindOriginMain: Number.isFinite(behind) ? behind : null,
|
|
117
|
+
dirty: rawStatus === null ? null : rawStatus !== '',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* One provenance read per engine checkout per process. The value is stable for
|
|
123
|
+
* a server's lifetime: changing engine source restarts the server
|
|
124
|
+
* (`engine-source-restart.ts`), so there is nothing to invalidate. Keyed by
|
|
125
|
+
* root rather than module-global so a test — or a process hosting two roots —
|
|
126
|
+
* cannot poison another's answer.
|
|
127
|
+
*/
|
|
128
|
+
const cache = new Map<string, Promise<EngineProvenance>>();
|
|
129
|
+
|
|
130
|
+
export function engineProvenance(engineRoot: string, run?: GitRunner): Promise<EngineProvenance> {
|
|
131
|
+
const key = canonical(engineRoot);
|
|
132
|
+
const cached = cache.get(key);
|
|
133
|
+
if (cached) return cached;
|
|
134
|
+
// Never let a rejection escape into a boot path or an HTTP handler.
|
|
135
|
+
const pending = computeEngineProvenance(engineRoot, run ?? createGitRunner(key)).catch(
|
|
136
|
+
() => UNKNOWN_ENGINE_PROVENANCE,
|
|
137
|
+
);
|
|
138
|
+
cache.set(key, pending);
|
|
139
|
+
return pending;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The ONE wording. The server banner, `vgai edit` and `vgai status` all print
|
|
144
|
+
* this exact string, so a reader comparing two surfaces is never left deciding
|
|
145
|
+
* whether they disagree or are merely phrased differently.
|
|
146
|
+
*/
|
|
147
|
+
export function formatEngineProvenanceLine(provenance: EngineProvenance | null): string {
|
|
148
|
+
if (
|
|
149
|
+
provenance === null ||
|
|
150
|
+
(provenance.branch === null &&
|
|
151
|
+
provenance.commit === null &&
|
|
152
|
+
provenance.behindOriginMain === null &&
|
|
153
|
+
provenance.dirty === null)
|
|
154
|
+
) {
|
|
155
|
+
return 'engine: provenance unavailable';
|
|
156
|
+
}
|
|
157
|
+
const branch = provenance.branch ?? 'detached';
|
|
158
|
+
const commit = provenance.commit ?? 'unknown commit';
|
|
159
|
+
const behind =
|
|
160
|
+
provenance.behindOriginMain === null
|
|
161
|
+
? 'distance from origin/main unknown'
|
|
162
|
+
: `${provenance.behindOriginMain} behind origin/main`;
|
|
163
|
+
const dirty = provenance.dirty === true ? ' [dirty]' : '';
|
|
164
|
+
const stale =
|
|
165
|
+
provenance.behindOriginMain !== null && provenance.behindOriginMain > 0
|
|
166
|
+
? ' — STALE: fixes merged to main are not running here'
|
|
167
|
+
: '';
|
|
168
|
+
return `engine: ${branch} @ ${commit} (${behind})${dirty}${stale}`;
|
|
169
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debounced, content-aware restart gate for the dev host's engine-source
|
|
3
|
+
* watcher (editor-session diagnosis cause 4). The watcher used to hand ANY
|
|
4
|
+
* chokidar event straight to the exit-75 restart handoff, which produced
|
|
5
|
+
* back-to-back restart storms for an actively-editing agent: a multi-file
|
|
6
|
+
* save burst restarted once per file (each relaunch caught the next event),
|
|
7
|
+
* and mtime-only touches / git branch switches restoring identical bytes
|
|
8
|
+
* restarted a process whose imported modules were not actually stale — the
|
|
9
|
+
* "zombie-tab reload loop". Two minimal suppressions, without changing what
|
|
10
|
+
* counts as watched:
|
|
11
|
+
*
|
|
12
|
+
* - **Content check**: each watched file's content hash is baselined during
|
|
13
|
+
* the watcher's initial scan and refreshed as events arrive; an event
|
|
14
|
+
* whose bytes match the last-seen hash is ignored entirely (only different
|
|
15
|
+
* bytes stale the Node process's imports). Unreadable/deleted paths hash
|
|
16
|
+
* to null and are treated as changed, so deletions still restart.
|
|
17
|
+
* - **Debounce**: a real change (re)arms a short quiet-window timer; the
|
|
18
|
+
* restart callback fires ONCE, after the burst goes quiet, reporting the
|
|
19
|
+
* burst's FIRST change (matching the old first-event-wins staleSource
|
|
20
|
+
* semantics). Once fired, the gate is latched — the process is exiting.
|
|
21
|
+
*
|
|
22
|
+
* Hashing and timers are injectable so the decision logic is unit-testable
|
|
23
|
+
* without filesystem or clock choreography (test-proportionality policy).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { createHash } from 'node:crypto';
|
|
27
|
+
import { readFileSync } from 'node:fs';
|
|
28
|
+
|
|
29
|
+
export interface EngineSourceChange {
|
|
30
|
+
changedPath: string;
|
|
31
|
+
changedAt: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** sha256 of a file's bytes, or null when unreadable (deleted, a directory,
|
|
35
|
+
* permission). Callers treat null as "changed" — a vanished source file
|
|
36
|
+
* stales imports exactly like different bytes do. */
|
|
37
|
+
export function hashFileContent(file: string): string | null {
|
|
38
|
+
try {
|
|
39
|
+
return createHash('sha256').update(readFileSync(file)).digest('hex');
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface EngineSourceRestartGate {
|
|
46
|
+
/** Record a file's boot-time content during the watcher's initial scan
|
|
47
|
+
* (chokidar's pre-`ready` add events) — served content IS boot content,
|
|
48
|
+
* since any earlier change already restarted the previous process. */
|
|
49
|
+
recordBaseline(file: string): void;
|
|
50
|
+
/** Handle a post-scan watcher event; arms/re-arms the quiet-window timer
|
|
51
|
+
* when the file's content actually differs from the last-seen bytes. */
|
|
52
|
+
handleEvent(event: string, file: string): void;
|
|
53
|
+
/** Cancel any pending restart (shutdown path). */
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createEngineSourceRestartGate(options: {
|
|
58
|
+
onRestart: (change: EngineSourceChange) => void;
|
|
59
|
+
/** Quiet window a burst must go silent for before the single restart fires. */
|
|
60
|
+
quietMs?: number;
|
|
61
|
+
hashFile?: (file: string) => string | null;
|
|
62
|
+
/** Timer seam (opaque handle) so tests drive the quiet window directly. */
|
|
63
|
+
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
64
|
+
clearTimer?: (timer: unknown) => void;
|
|
65
|
+
now?: () => Date;
|
|
66
|
+
}): EngineSourceRestartGate {
|
|
67
|
+
const quietMs = options.quietMs ?? 1000;
|
|
68
|
+
const hashFile = options.hashFile ?? hashFileContent;
|
|
69
|
+
const setTimer =
|
|
70
|
+
options.setTimer ?? ((fn: () => void, ms: number): unknown => setTimeout(fn, ms));
|
|
71
|
+
const clearTimer =
|
|
72
|
+
options.clearTimer ?? ((timer: unknown) => clearTimeout(timer as NodeJS.Timeout));
|
|
73
|
+
const now = options.now ?? (() => new Date());
|
|
74
|
+
|
|
75
|
+
/** file -> last-seen content hash (baseline scan, then refreshed per event). */
|
|
76
|
+
const lastSeen = new Map<string, string>();
|
|
77
|
+
let pending: EngineSourceChange | null = null;
|
|
78
|
+
let timer: unknown = null;
|
|
79
|
+
let fired = false;
|
|
80
|
+
|
|
81
|
+
const fire = (): void => {
|
|
82
|
+
timer = null;
|
|
83
|
+
if (fired || pending === null) return;
|
|
84
|
+
fired = true;
|
|
85
|
+
options.onRestart(pending);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
recordBaseline(file: string): void {
|
|
90
|
+
const hash = hashFile(file);
|
|
91
|
+
if (hash !== null) lastSeen.set(file, hash);
|
|
92
|
+
},
|
|
93
|
+
handleEvent(event: string, file: string): void {
|
|
94
|
+
if (fired) return;
|
|
95
|
+
if (event === 'unlink' || event === 'unlinkDir') {
|
|
96
|
+
// Deletions are always real changes (old any-event behavior kept);
|
|
97
|
+
// drop the hash so a later re-add with identical bytes still counts
|
|
98
|
+
// as a change relative to "the file was gone".
|
|
99
|
+
lastSeen.delete(file);
|
|
100
|
+
} else {
|
|
101
|
+
const hash = hashFile(file);
|
|
102
|
+
if (hash !== null && hash === lastSeen.get(file)) return; // mtime-only touch
|
|
103
|
+
if (hash !== null) lastSeen.set(file, hash);
|
|
104
|
+
else lastSeen.delete(file);
|
|
105
|
+
}
|
|
106
|
+
if (pending === null) {
|
|
107
|
+
pending = { changedPath: file, changedAt: now().toISOString() };
|
|
108
|
+
}
|
|
109
|
+
if (timer !== null) clearTimer(timer);
|
|
110
|
+
timer = setTimer(fire, quietMs);
|
|
111
|
+
},
|
|
112
|
+
dispose(): void {
|
|
113
|
+
if (timer !== null) clearTimer(timer);
|
|
114
|
+
timer = null;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|