@vgai/editor 0.2.0 → 0.4.0-canary.20260715.1
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/README.md +24 -0
- package/dist/assets/axe-CyiTVRi6.js +30 -0
- package/dist/assets/browserAll-fGZ_P1P8.js +1 -0
- package/dist/assets/create-runtime-BzSHBTYw.js +1 -0
- package/dist/assets/game-DQ7YHNS8.js +1 -0
- package/dist/assets/game-DX6Obomf.js +1 -0
- package/dist/assets/game-DoUJj8vY.js +1 -0
- package/dist/assets/{game-xsOho_wg.js → game-HqybI_X7.js} +3 -3
- package/dist/assets/game-NUAnyPkS.js +1 -0
- package/dist/assets/game-ck_dNIE6.js +1 -0
- package/dist/assets/{game-pt_1lQLv.js → game-hIf5LD4E.js} +1 -1
- package/dist/assets/game-loop-o2CcBMFY.js +1 -0
- package/dist/assets/{game-source-CvidN7AB.js → game-source-C8HM7gbP.js} +1 -1
- package/dist/assets/{game-BI111o5f.js → game-uR_lv3Mp.js} +3 -3
- package/dist/assets/index-B-HA1uu3.js +15412 -0
- package/dist/assets/{index-Bp5cecD6.js → index-CSBAZwMx.js} +2 -2
- package/dist/assets/{index-DL7RcQ0n.js → index-CoSRCKPS.js} +1 -1
- package/dist/assets/{index-Cs8xwv3e.js → index-D93bBuUl.js} +1 -1
- package/dist/assets/index-bu01fqch.css +1 -0
- package/dist/assets/ingest-siblings-CCkkE2h7.js +1 -0
- package/dist/assets/mount-manifest-B2ECssU2.js +1 -0
- package/dist/assets/react-18-Bmj05S8J.js +1 -0
- package/dist/assets/webworkerAll-BC6gMOLZ.js +1 -0
- package/dist/assets/world-documents-DXPcAEA0.js +1 -0
- package/dist/index.html +15 -13
- package/dist/ingest/racing-game/vgai.game.json +5 -4
- package/dist/ingest/simcity/vgai.game.json +5 -4
- package/dist/ingest/tanks/vgai.game.json +5 -4
- package/dist/project-scripts.bundle.js +3917 -0
- package/dist/scenes/untitled.vscn.json +2 -0
- package/dist-server/packaged.mjs +7718 -3436
- package/package.json +31 -4
- package/scripts/probe-reporter.ts +349 -0
- package/scripts/run-probe.ts +2260 -0
- package/server/asset-catalog-audit.ts +268 -0
- package/server/asset-catalog-v2.ts +146 -0
- package/server/asset-library-routes.ts +276 -58
- package/server/canonical-path.ts +1 -1
- package/server/dev.ts +148 -44
- package/server/editor-server.ts +888 -61
- package/server/editor-sse.ts +115 -4
- package/server/harness-chat-service.ts +796 -0
- package/server/local-asset-catalog.ts +157 -22
- package/server/packaged.ts +71 -21
- package/server/process-shutdown.ts +136 -0
- package/server/prod.ts +9 -6
- package/server/project-hmr-files.ts +41 -0
- package/server/project-operations.ts +343 -0
- package/server/project-optimize-deps-entries.ts +4 -4
- package/server/project-output-writer.ts +156 -0
- package/server/project-validation.ts +140 -0
- package/server/project-view.ts +8 -50
- package/server/server-utils.ts +75 -10
- package/server/session-registry.ts +12 -0
- package/src/EditorContext.tsx +107 -3
- package/src/action-registry.ts +115 -17
- package/src/adapter-resolver.ts +213 -478
- package/src/asset-events.ts +64 -1
- package/src/asset-preview.ts +906 -0
- package/src/asset-selection.ts +121 -0
- package/src/asset-workflow/asset-capabilities.ts +259 -0
- package/src/asset-workflow/asset-editor-errors.ts +46 -0
- package/src/asset-workflow/asset-import-pipeline.ts +176 -0
- package/src/asset-workflow/asset-library-query.ts +107 -0
- package/src/asset-workflow/asset-types.ts +192 -0
- package/src/asset-workflow/asset-workflow-quality.ts +65 -0
- package/src/asset-workflow/import-contract.ts +113 -0
- package/src/asset-workflow/model-conversion.ts +126 -0
- package/src/asset-workflow/model-inspection.ts +185 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +153 -0
- package/src/asset-workflow/project-asset-operations.ts +119 -0
- package/src/asset-workflow/thumbnail-system.ts +172 -0
- package/src/authoring/active-systems.ts +15 -0
- package/src/authoring/boundary-authoring-adapter.ts +16 -5
- package/src/authoring/composite-authoring-adapter.ts +305 -124
- package/src/authoring/design-time-layers.ts +300 -117
- package/src/authoring/edit-mode-authoring.ts +278 -293
- package/src/authoring/embed-only-adapter.ts +4 -2
- package/src/authoring/first-party-hierarchy-menu.ts +7 -2
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
- package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
- package/src/authoring/first-party-inspector/register.tsx +13 -2
- package/src/authoring/ingest-authoring-adapter.ts +142 -45
- package/src/authoring/mount-failure-report.ts +5 -5
- package/src/authoring/provenance.ts +41 -0
- package/src/authoring/react-dom-authoring-adapter.ts +92 -92
- package/src/authoring/react-inspector-section.tsx +489 -51
- package/src/authoring/react-world-authoring-adapter.ts +541 -306
- package/src/authoring/story-overlay.ts +1 -1
- package/src/authoring/story-types.ts +11 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
- package/src/authoring/viewport-tool-context.ts +78 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +186 -0
- package/src/authoring/world-pan-state.ts +33 -17
- package/src/authoring/world-session-state.ts +1 -1
- package/src/authoring/world2d-authoring-adapter.ts +109 -52
- package/src/availability-tick.ts +56 -0
- package/src/browser-play-entry.ts +5 -5
- package/src/browser-project-scripts.ts +1 -0
- package/src/build-identity.ts +15 -0
- package/src/command-listener.ts +893 -19
- package/src/components/AlignToolbar.tsx +74 -63
- package/src/components/AppRoot.tsx +193 -26
- package/src/components/ApplicationMenus.tsx +272 -0
- package/src/components/AssetBrowser.tsx +1236 -296
- package/src/components/AssetEditorShell.tsx +141 -0
- package/src/components/AssetImportDetails.tsx +218 -0
- package/src/components/AssetLibraryPanel.tsx +14 -0
- package/src/components/CameraInfo.tsx +11 -20
- package/src/components/CenterDocuments.tsx +585 -0
- package/src/components/CommandPalette.tsx +75 -30
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +57 -112
- package/src/components/DebugConsolePanel.tsx +461 -0
- package/src/components/DebuggerPanel.tsx +165 -0
- package/src/components/DefaultEditorLayout.tsx +167 -231
- package/src/components/GameHierarchy.tsx +873 -290
- package/src/components/GamePerformanceOverlay.tsx +46 -0
- package/src/components/HarnessChatPanel.tsx +2352 -0
- package/src/components/HistoryPanel.tsx +226 -0
- package/src/components/Inspector.tsx +153 -99
- package/src/components/InspectorToolSection.tsx +6 -4
- package/src/components/MountFailureBanner.tsx +31 -48
- package/src/components/NetworkSection.tsx +25 -14
- package/src/components/OnlineAssetBrowser.tsx +1116 -344
- package/src/components/OverlayReportBanner.tsx +38 -64
- package/src/components/PerformancePanel.tsx +206 -0
- package/src/components/PlayBar.tsx +93 -263
- package/src/components/PrefabSaveDialog.tsx +55 -127
- package/src/components/ProjectHeader.css +60 -0
- package/src/components/ProjectHeader.tsx +170 -105
- package/src/components/ProjectScreen.tsx +246 -184
- package/src/components/ReactCanvasControls.tsx +185 -0
- package/src/components/ResolutionPicker.tsx +85 -0
- package/src/components/RootActivateDialog.tsx +39 -68
- package/src/components/SaveDialog.tsx +55 -127
- package/src/components/SaveStatus.tsx +11 -29
- package/src/components/StartupLoadingScreen.tsx +96 -0
- package/src/components/StateWatchPanel.tsx +302 -0
- package/src/components/StatsOverlay.tsx +12 -5
- package/src/components/StatusBar.tsx +64 -0
- package/src/components/ThemeManagerDialog.css +89 -0
- package/src/components/ThemeManagerDialog.tsx +371 -0
- package/src/components/ToolHost.tsx +26 -15
- package/src/components/Toolbar.tsx +117 -214
- package/src/components/VgaiVoxelLogo.tsx +89 -0
- package/src/components/ViewportOverlay.tsx +97 -172
- package/src/components/ViewportPanel.tsx +130 -31
- package/src/components/WorkspaceDock.tsx +891 -0
- package/src/components/WorldSelectionOverlay.tsx +51 -63
- package/src/components/WorldTextEditor.tsx +9 -5
- package/src/components/asset-documents.tsx +390 -0
- package/src/components/asset-editor-persistence.ts +44 -0
- package/src/components/asset-selection-section.tsx +338 -0
- package/src/components/asset-viewers/AudioViewer.tsx +73 -21
- package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
- package/src/components/asset-viewers/ImageViewer.tsx +43 -11
- package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
- package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
- package/src/components/asset-viewers/ModelViewer.tsx +805 -121
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
- package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
- package/src/components/asset-workflow.css +620 -0
- package/src/components/build-documents.tsx +696 -0
- package/src/components/core-utilities.tsx +75 -0
- package/src/components/data-documents.tsx +364 -0
- package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
- package/src/components/debugger-panel-state.ts +57 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
- package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
- package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
- package/src/components/inspector-widgets/FontPicker.tsx +13 -21
- package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
- package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
- package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
- package/src/components/inspector-widgets/shared.tsx +90 -49
- package/src/components/inspectors/AnimationSection.tsx +39 -40
- package/src/components/inspectors/AudioSection.tsx +28 -39
- package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
- package/src/components/inspectors/CameraSection.tsx +14 -23
- package/src/components/inspectors/ComponentsSection.tsx +39 -43
- package/src/components/inspectors/JointsSection.tsx +34 -43
- package/src/components/inspectors/LightSection.tsx +90 -35
- package/src/components/inspectors/MaterialSection.tsx +137 -174
- package/src/components/inspectors/MeshSection.tsx +9 -17
- package/src/components/inspectors/NavigationSection.tsx +8 -15
- package/src/components/inspectors/ParticleSection.tsx +91 -76
- package/src/components/inspectors/PhysicsSection.tsx +42 -46
- package/src/components/inspectors/PostProcessingSection.tsx +28 -36
- package/src/components/inspectors/RenderingSection.tsx +12 -20
- package/src/components/inspectors/ShadowSection.tsx +7 -8
- package/src/components/inspectors/SplineSection.tsx +31 -42
- package/src/components/inspectors/TransformSection.tsx +14 -22
- package/src/components/patterns/Dialog.tsx +95 -0
- package/src/components/patterns/Fields.tsx +44 -0
- package/src/components/patterns/List.tsx +25 -0
- package/src/components/patterns/StateSurface.tsx +40 -0
- package/src/components/patterns/Surfaces.tsx +115 -0
- package/src/components/patterns/Tabs.tsx +80 -0
- package/src/components/patterns/Toolbar.tsx +51 -0
- package/src/components/patterns/Tree.tsx +72 -0
- package/src/components/primitives/Button.tsx +46 -0
- package/src/components/primitives/ColorInput.tsx +11 -47
- package/src/components/primitives/EditorIcon.tsx +99 -0
- package/src/components/primitives/FormControls.tsx +71 -0
- package/src/components/primitives/JsonInput.tsx +15 -2
- package/src/components/primitives/Layout.tsx +100 -0
- package/src/components/primitives/Menu.tsx +105 -0
- package/src/components/primitives/NumberInput.tsx +8 -35
- package/src/components/primitives/Panel.tsx +49 -44
- package/src/components/primitives/SectionHeader.tsx +10 -28
- package/src/components/primitives/Text.tsx +46 -0
- package/src/components/primitives/Tooltip.tsx +20 -12
- package/src/components/primitives/Vec3Input.tsx +3 -0
- package/src/components/primitives/banner-tones.ts +33 -0
- package/src/components/primitives/editor-icons.ts +102 -0
- package/src/components/primitives/panel-header-styles.ts +30 -0
- package/src/components/project-operation-documents.tsx +226 -0
- package/src/components/status-contributions.tsx +175 -0
- package/src/components/story-documents-register.ts +15 -0
- package/src/components/story-documents.tsx +1185 -0
- package/src/components/tool-documents.tsx +110 -0
- package/src/components/workspace-dock.css +293 -0
- package/src/components/workspace-history.ts +33 -0
- package/src/components/world-documents.tsx +145 -0
- package/src/components/xstate-documents.tsx +209 -0
- package/src/composite-screenshot.ts +248 -0
- package/src/design-system/index.ts +70 -0
- package/src/design-system-stories/Foundation.stories.tsx +183 -0
- package/src/design-system-stories/Patterns.stories.tsx +251 -0
- package/src/design-system-stories/Workspace.stories.tsx +538 -0
- package/src/doctor/report.ts +57 -13
- package/src/editor-api.ts +295 -31
- package/src/editor-compatibility.ts +200 -0
- package/src/editor-console.ts +35 -17
- package/src/editor-hotkeys.ts +85 -33
- package/src/editor-store.ts +538 -233
- package/src/editor-viewport.ts +160 -34
- package/src/entity-factory.ts +14 -3
- package/src/gizmo-registry.ts +21 -5
- package/src/harness-chat-editor-context.ts +37 -0
- package/src/harness-chat-indicator.ts +68 -0
- package/src/harness-chat-layout.ts +53 -0
- package/src/harness-chat-lifecycle.ts +170 -0
- package/src/harness-chat-reconnect.ts +85 -0
- package/src/harness-chat-types.ts +409 -0
- package/src/hierarchy-drop.ts +50 -0
- package/src/hierarchy-expansion-state.ts +100 -0
- package/src/hierarchy-menu-registry.ts +1 -1
- package/src/hierarchy-projection.ts +76 -0
- package/src/history/editor-session.ts +21 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-service.ts +904 -0
- package/src/history/json-history-resource.ts +165 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +266 -0
- package/src/history/project-root-history-backends.ts +53 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +108 -0
- package/src/history/source-history-backend.ts +374 -0
- package/src/history/types.ts +100 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hosted-example.ts +12 -2
- package/src/hotkeys.ts +89 -18
- package/src/ingest/discovery-react.ts +9 -10
- package/src/ingest/discovery.ts +4 -4
- package/src/ingest/discovery2d.ts +5 -5
- package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
- package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
- package/src/ingest/games/games-fps/vgai.game.json +3 -3
- package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
- package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
- package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
- package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
- package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
- package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
- package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
- package/src/ingest/types.ts +1 -1
- package/src/ingest-mode.ts +47 -52
- package/src/ingest-siblings.ts +23 -33
- package/src/initial-scene.ts +1 -2
- package/src/main.tsx +16 -8
- package/src/manifest-project.ts +10 -12
- package/src/model-thumbnail.ts +261 -26
- package/src/module-mode.ts +44 -50
- package/src/play-log-events.ts +26 -0
- package/src/play-mode.ts +495 -401
- package/src/play-scene-adoption.ts +23 -0
- package/src/playground/main.ts +68 -24
- package/src/playwright-shim.ts +473 -0
- package/src/project-manager.ts +90 -10
- package/src/project-operations.ts +112 -0
- package/src/project-story-discovery.ts +25 -0
- package/src/project-tool-discovery.ts +43 -0
- package/src/scene-serializer.ts +0 -6
- package/src/scene-sync.ts +7 -2
- package/src/script-loader.ts +3 -3
- package/src/skeleton-helper.ts +57 -0
- package/src/storage/seed.ts +68 -8
- package/src/stories/StoryPreviewMount.tsx +265 -0
- package/src/stories/action-stories.fixture.tsx +44 -0
- package/src/stories/button-stories.fixture.tsx +50 -0
- package/src/stories/compose-project-stories.ts +182 -0
- package/src/stories/loader-stories.fixture.tsx +53 -0
- package/src/stories/story-discovery.ts +130 -0
- package/src/stories/story-registry.ts +146 -0
- package/src/theme-css.ts +1049 -0
- package/src/theme-library.ts +238 -0
- package/src/theme-preference.ts +130 -0
- package/src/theme.ts +527 -0
- package/src/tool-loader.ts +80 -24
- package/src/ui-source/inspect.ts +27 -4
- package/src/ui-source/source-write-backend.ts +83 -22
- package/src/widgets/index.ts +9 -18
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-dock-controller.ts +64 -0
- package/src/workspace-document-ids.ts +5 -0
- package/src/workspace-document-registry.ts +367 -0
- package/src/workspace-state-persistence.ts +455 -0
- package/src/workspace-status-registry.ts +110 -0
- package/src/workspace-utility-commands.ts +49 -0
- package/src/workspace-utility-registry.ts +161 -0
- package/src/world-document-routing.ts +16 -0
- package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
- package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
- package/src/xstate-inspect/use-live-actor-state.ts +39 -0
- package/src/xstate-inspect/xstate-graph.ts +265 -0
- package/src/xstate-inspect/xstate-layout.ts +76 -0
- package/src/z-index.ts +26 -0
- package/template/.claude/skills/editor/SKILL.md +129 -90
- package/template/AGENTS.md +1110 -0
- package/template/CLAUDE.md +1 -512
- package/template/IDIOMS.md +50 -0
- package/template/check-idioms.ts +706 -0
- package/template/manifest-entry-modules-plugin.ts +53 -0
- package/template/package.json +13 -4
- package/template/public/scenes/default.scn2d.json +6 -0
- package/template/public/scenes/default.vscn.json +7 -1
- package/template/public/scenes/untitled.vscn.json +10 -0
- package/template/scripts/playtest.ts +40 -0
- package/template/server/loopback.ts +75 -0
- package/template/server/rooms/arena-room.ts +69 -1
- package/template/server/rooms/game-room.ts +68 -1
- package/template/src/data/README.md +9 -4
- package/template/src/data/data-ref.ts +35 -0
- package/template/src/data/tuning.schema.ts +1 -1
- package/template/src/main.ts +41 -62
- package/template/src/manifest-entry-modules.d.ts +3 -0
- package/template/src/operations/README.md +88 -0
- package/template/src/runtime/pixi-adapter.ts +24 -0
- package/template/src/scripts/components/data-spinner.ts +43 -2
- package/template/src/scripts/components/spin-lap-math.ts +27 -0
- package/template/src/tools/README.md +5 -3
- package/template/src/tools/example.tool.tsx +13 -7
- package/template/src/ui/Button.stories.tsx +66 -0
- package/template/src/ui/RebindPanel.tsx +2 -6
- package/template/src/ui/game.tsx +17 -0
- package/template/tests/acceptance/example.spec.ts +64 -0
- package/template/tests/logic/example.test.ts +31 -0
- package/template/tests/logic/room.test.ts +100 -0
- package/template/tests/tsconfig.json +30 -0
- package/template/validate-manifest.ts +5 -5
- package/template/validate-scenes.ts +50 -18
- package/template/vgai.game.json +20 -13
- package/template/vite.config.ts +77 -16
- package/vite-plugin-ui-oid.ts +261 -17
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
- package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
- package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
- package/dist/assets/browserAll-CEkWYlFk.js +0 -1
- package/dist/assets/create-runtime-DYIWr3va.js +0 -1
- package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
- package/dist/assets/game-8SDZPPgH.js +0 -1
- package/dist/assets/game-CevorxyV.js +0 -1
- package/dist/assets/game-Di7XlywD.js +0 -1
- package/dist/assets/game-DjnCaw8R.js +0 -1
- package/dist/assets/game-Q9A0aXSV.js +0 -1
- package/dist/assets/index-CPVV6pjw.js +0 -1
- package/dist/assets/index-CpOnYyeD.js +0 -213
- package/dist/assets/index-Dr8H_6Rl.js +0 -1
- package/dist/assets/index-_bCaipUk.js +0 -13650
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
- package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
- package/dist/assets/scene-query-BivAHhJu.js +0 -1
- package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
- package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
- package/src/authoring/scene-ui-layer.ts +0 -485
- package/src/authoring/ui-authoring-adapter.ts +0 -325
- package/src/authoring/ui-hierarchy-menu.ts +0 -40
- package/src/authoring/ui-inspector-sections.tsx +0 -156
- package/src/authoring/undo-timeline.ts +0 -145
- package/src/components/BottomPanel.tsx +0 -326
- package/src/components/BuildPanel.tsx +0 -455
- package/src/components/FolderTree.tsx +0 -179
- package/src/components/RightPanel.tsx +0 -166
- package/src/components/WorldContextMenu.tsx +0 -257
- package/src/components/asset-viewers/AssetViewer.tsx +0 -38
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
- package/src/scene-ui-mode/active-scene-ui.ts +0 -58
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
- package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
- package/src/ui-editor/inspector.tsx +0 -563
- package/src/ui-editor/node-ops.ts +0 -266
- package/src/ui-editor/overlay.tsx +0 -380
- package/src/ui-editor/palette.tsx +0 -114
- package/src/ui-editor/react-store.ts +0 -78
- package/src/ui-editor/timeline-panel.tsx +0 -443
- package/src/ui-editor/ui-edit-store.ts +0 -552
- package/src/ui-editor/ui-prefab.ts +0 -95
- package/template/project.json +0 -9
- package/template/src/scripts/main.ts +0 -94
- package/template/src/ui/DefaultHud.tsx +0 -40
- package/template/src/ui/game-state.tsx +0 -37
- package/template/src/ui/mount-default-hud.tsx +0 -19
- package/template/src/ui/mount-ui.ts +0 -15
- package/vite-plugin-react-world-provider.ts +0 -81
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W5 (docs/EDITOR-WORKSPACE-SHELL-TODO.md §6.2/§9 W5 — inventory rows
|
|
3
|
+
* B17/R8) — PERSISTED WORKSPACE STATE: Dockview's serialized panel tree plus
|
|
4
|
+
* the open authored documents + active document. Restored on boot, written
|
|
5
|
+
* through (debounced) on every change. Dockview owns every physical concern:
|
|
6
|
+
* groups, sizes, tab order, collapsed edge groups, floating panels, and the
|
|
7
|
+
* maximized group. Manual hierarchy openness is NOT here — it persists in its
|
|
8
|
+
* own store (`hierarchy-expansion-state.ts`, §6.1), same key discipline.
|
|
9
|
+
*
|
|
10
|
+
* STORAGE DECISION (recorded): `localStorage`, key
|
|
11
|
+
* `vgai:workspace-state:v8`, holding a map keyed PER PROJECT by
|
|
12
|
+
* `getCurrentProject().rootPath` (the same project identity
|
|
13
|
+
* `hierarchy-expansion-state.ts` established — it prevents one game's layout
|
|
14
|
+
* leaking into another, and `rootPath` is the durable cross-session
|
|
15
|
+
* identity). Why localStorage and NOT a project-scoped metadata file
|
|
16
|
+
* (including the game manifest): layout is per-user/per-machine CONVENIENCE,
|
|
17
|
+
* not project
|
|
18
|
+
* truth — writing it into the project would dirty the user's repo from mere
|
|
19
|
+
* UI fiddling (the harness fence "no persistent scene/data edits" names
|
|
20
|
+
* exactly this hazard), would break read-only hosted `?project=` sessions,
|
|
21
|
+
* and would fight the precedent that hierarchy openness (the other §6.2
|
|
22
|
+
* persisted preference) already lives in localStorage.
|
|
23
|
+
*
|
|
24
|
+
* DOCUMENT RESTORE RULES (§9/W5 "persist+restore only documents that can
|
|
25
|
+
* truthfully reopen — verify on restore, drop silently if gone"):
|
|
26
|
+
* - scene/game/world: NEVER persisted here. Scene and Game are opened
|
|
27
|
+
* unconditionally by `CenterDocuments.syncCenterDocuments`; manifest
|
|
28
|
+
* world documents are rediscovered from the active composite. Their
|
|
29
|
+
* Dockview panels still persist as layout geometry, but none need a
|
|
30
|
+
* synthetic restorable-document record.
|
|
31
|
+
* - asset documents: persisted only for real file-backed specs (a serving
|
|
32
|
+
* path). Verified via `projectFileExists()` (storage-first stat, HTTP HEAD
|
|
33
|
+
* fallback — an existence-only probe; unlike the prefab/scene loaders'
|
|
34
|
+
* `readProjectTextFile`, this never downloads the asset body just to check
|
|
35
|
+
* it is still there). Asset-Lab (`asset-lab:entity:*`) and unresolved
|
|
36
|
+
* online (`online:*`) documents are NOT persisted: a live-entity lab
|
|
37
|
+
* session and a remote detail view are not truthfully re-resolvable from a
|
|
38
|
+
* cold boot.
|
|
39
|
+
* - data documents: verified against `loadedDataFiles()` after an awaited
|
|
40
|
+
* `refreshDataFiles()` — a deleted/renamed `.data.json` is dropped.
|
|
41
|
+
* - tool documents: `openToolDocument` self-verifies against the discovered
|
|
42
|
+
* tool store (awaited `refreshProjectTools()` first) — unknown ids open
|
|
43
|
+
* nothing.
|
|
44
|
+
* - story documents: verified against the story registry after an awaited
|
|
45
|
+
* `refreshProjectStories()` — module must load ok AND still export the
|
|
46
|
+
* story name.
|
|
47
|
+
* - static shell documents (`Data Assets`, `Build Profiles`): always
|
|
48
|
+
* truthfully reopenable (Build Profiles only in server mode). Asset
|
|
49
|
+
* Library is a permanent navigator tab, so Dockview restores its physical
|
|
50
|
+
* placement without fabricating a center document.
|
|
51
|
+
* - live-observation tools are never center documents. A legacy persisted
|
|
52
|
+
* `workspace:profiler` entry migrates into the bottom Debugger; live
|
|
53
|
+
* XState machines cannot restore because reload ended their bindings.
|
|
54
|
+
*
|
|
55
|
+
* Write-through is debounced (300ms trailing) and flushed on `pagehide`, so
|
|
56
|
+
* an immediate reload still captures the last change. Subscriptions install
|
|
57
|
+
* only AFTER the async restore finishes — a half-restored open-set must
|
|
58
|
+
* never overwrite the stored one. That wait is BOUNDED (`RESTORE_TIMEOUT_MS`):
|
|
59
|
+
* a hung verification fetch (e.g. a stalled asset-existence probe) must never
|
|
60
|
+
* block write-through + pagehide-flush installation for the whole session —
|
|
61
|
+
* layout/open-tab state is convenience state, not project truth, so
|
|
62
|
+
* availability beats completeness here. If the restore itself keeps running
|
|
63
|
+
* past the timeout, its later document-opens still land normally; only the
|
|
64
|
+
* subscription install is time-boxed, not the restore itself.
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
import type { DockviewApi, SerializedDockview } from 'dockview-react';
|
|
68
|
+
import { assetDocumentSpec, openAssetDocument } from './components/asset-documents';
|
|
69
|
+
import {
|
|
70
|
+
BUILD_PROFILES_DOCUMENT_ID,
|
|
71
|
+
openBuildProfilesDocument,
|
|
72
|
+
} from './components/build-documents';
|
|
73
|
+
import { SCENE_DOCUMENT_ID } from './components/CenterDocuments';
|
|
74
|
+
import {
|
|
75
|
+
DATA_ASSETS_DOCUMENT_ID,
|
|
76
|
+
loadedDataFiles,
|
|
77
|
+
openDataAssetsDocument,
|
|
78
|
+
openDataDocument,
|
|
79
|
+
refreshDataFiles,
|
|
80
|
+
} from './components/data-documents';
|
|
81
|
+
import { openDebuggerPanel } from './components/debugger-panel-state';
|
|
82
|
+
import {
|
|
83
|
+
openStoryDocsDocument,
|
|
84
|
+
openStoryDocument,
|
|
85
|
+
storyDocumentState,
|
|
86
|
+
} from './components/story-documents';
|
|
87
|
+
import { openToolDocument } from './components/tool-documents';
|
|
88
|
+
import { projectFileExists } from './editor-api';
|
|
89
|
+
import { getEditorMode } from './editor-mode';
|
|
90
|
+
import type { AssetKind } from './editor-store';
|
|
91
|
+
import {
|
|
92
|
+
harnessChat,
|
|
93
|
+
hydrateHarnessChatLayout,
|
|
94
|
+
subscribeHarnessChatLayout,
|
|
95
|
+
} from './harness-chat-layout';
|
|
96
|
+
import { getCurrentProject } from './project-manager';
|
|
97
|
+
import { getProjectStoryModules, refreshProjectStories } from './stories/story-registry';
|
|
98
|
+
import { refreshProjectTools } from './tool-loader';
|
|
99
|
+
import {
|
|
100
|
+
activateWorkspaceDocument,
|
|
101
|
+
activeWorkspaceDocumentId,
|
|
102
|
+
openWorkspaceDocuments,
|
|
103
|
+
subscribeWorkspaceDocuments,
|
|
104
|
+
} from './workspace-document-registry';
|
|
105
|
+
|
|
106
|
+
// v6 flattens the short-lived nested Project > Assets > source navigation.
|
|
107
|
+
// Local Assets and the online Library are peer Dockview tabs in the visible
|
|
108
|
+
// group below Hierarchy; Data and Stories keep their contextual/document
|
|
109
|
+
// entry points instead of masquerading as permanent navigation locations.
|
|
110
|
+
// v7 resets v6's short-lived four-column layout: Agent moved from the left to
|
|
111
|
+
// the right and the role-specific default widths replaced equalized columns.
|
|
112
|
+
// v8 introduces one isolated center document per manifest world plus the
|
|
113
|
+
// world-surface/resource tab-family seam. A v7 layout cannot name those
|
|
114
|
+
// panels and can restore center documents into a navigator group, so reset
|
|
115
|
+
// that physical layout once instead of guessing at user intent.
|
|
116
|
+
const STORAGE_KEY = 'vgai:workspace-state:v8';
|
|
117
|
+
const WRITE_DEBOUNCE_MS = 300;
|
|
118
|
+
/** Bound on the document-restore verification before write-through installs
|
|
119
|
+
* anyway (module header's "wait is BOUNDED" rule). */
|
|
120
|
+
const RESTORE_TIMEOUT_MS = 10_000;
|
|
121
|
+
const LEGACY_PROFILER_DOCUMENT_ID = 'workspace:profiler';
|
|
122
|
+
|
|
123
|
+
/** One persisted, restorable document (see the module header's rules). */
|
|
124
|
+
export type PersistedDocument =
|
|
125
|
+
| { readonly t: 'asset'; readonly id: string; readonly path: string; readonly assetKind: string }
|
|
126
|
+
| { readonly t: 'data'; readonly id: string; readonly path: string }
|
|
127
|
+
| { readonly t: 'tool'; readonly id: string; readonly toolId: string }
|
|
128
|
+
| {
|
|
129
|
+
readonly t: 'story';
|
|
130
|
+
readonly id: string;
|
|
131
|
+
readonly modulePath: string;
|
|
132
|
+
readonly storyName: string;
|
|
133
|
+
readonly view?: 'preview' | 'docs';
|
|
134
|
+
}
|
|
135
|
+
| { readonly t: 'static'; readonly id: string };
|
|
136
|
+
|
|
137
|
+
export interface PersistedWorkspaceState {
|
|
138
|
+
/** Dockview owns Agent geometry; this flag records whether the optional panel exists. */
|
|
139
|
+
readonly chat?: { collapsed: boolean };
|
|
140
|
+
readonly layout?: SerializedDockview;
|
|
141
|
+
readonly documents?: { open: PersistedDocument[]; activeId: string | null };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The narrow store surface every document open function needs (they all
|
|
145
|
+
* declare the same T6.3 hand-off contract). */
|
|
146
|
+
export interface WorkspaceStateStore {
|
|
147
|
+
setActiveViewportTab(tab: 'scene' | 'game'): void;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function browserStorage(): Storage | null {
|
|
151
|
+
try {
|
|
152
|
+
return typeof window === 'undefined' ? null : window.localStorage;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function projectIdentity(): string {
|
|
159
|
+
return getCurrentProject()?.rootPath ?? '__unscoped__';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Read one project's persisted state (exported for tests). */
|
|
163
|
+
export function readPersistedWorkspaceState(projectId: string): PersistedWorkspaceState | null {
|
|
164
|
+
const storage = browserStorage();
|
|
165
|
+
if (!storage) return null;
|
|
166
|
+
try {
|
|
167
|
+
const all = JSON.parse(storage.getItem(STORAGE_KEY) ?? '{}') as Record<
|
|
168
|
+
string,
|
|
169
|
+
PersistedWorkspaceState
|
|
170
|
+
>;
|
|
171
|
+
return all[projectId] ?? null;
|
|
172
|
+
} catch {
|
|
173
|
+
// A corrupt preference must never prevent the editor from opening —
|
|
174
|
+
// same failure physics as hierarchy-expansion-state.ts.
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function writePersistedWorkspaceState(projectId: string, state: PersistedWorkspaceState): void {
|
|
180
|
+
const storage = browserStorage();
|
|
181
|
+
if (!storage) return;
|
|
182
|
+
try {
|
|
183
|
+
const all = JSON.parse(storage.getItem(STORAGE_KEY) ?? '{}') as Record<
|
|
184
|
+
string,
|
|
185
|
+
PersistedWorkspaceState
|
|
186
|
+
>;
|
|
187
|
+
all[projectId] = state;
|
|
188
|
+
storage.setItem(STORAGE_KEY, JSON.stringify(all));
|
|
189
|
+
} catch {
|
|
190
|
+
// Layout is convenience state; storage denial/quota is non-fatal.
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Serialize the restorable half of one open document, or `null` for the
|
|
196
|
+
* kinds that must not persist (scene/game/xstate/asset-lab/online — module
|
|
197
|
+
* header). Exported for tests.
|
|
198
|
+
*/
|
|
199
|
+
export function persistableDocument(doc: {
|
|
200
|
+
descriptor: { id: string; kind: string };
|
|
201
|
+
}): PersistedDocument | null {
|
|
202
|
+
const { id, kind } = doc.descriptor;
|
|
203
|
+
switch (kind) {
|
|
204
|
+
case 'asset': {
|
|
205
|
+
const spec = assetDocumentSpec(id);
|
|
206
|
+
if (!spec || !spec.assetPath || spec.online || spec.entityId !== undefined) return null;
|
|
207
|
+
return { t: 'asset', id, path: spec.assetPath, assetKind: spec.kind };
|
|
208
|
+
}
|
|
209
|
+
case 'data':
|
|
210
|
+
return id === DATA_ASSETS_DOCUMENT_ID
|
|
211
|
+
? { t: 'static', id }
|
|
212
|
+
: { t: 'data', id, path: id.slice('data:'.length) };
|
|
213
|
+
case 'tool':
|
|
214
|
+
return { t: 'tool', id, toolId: id.slice('tool:'.length) };
|
|
215
|
+
case 'story': {
|
|
216
|
+
const state = storyDocumentState(id);
|
|
217
|
+
if (!state) return null;
|
|
218
|
+
return {
|
|
219
|
+
t: 'story',
|
|
220
|
+
id,
|
|
221
|
+
modulePath: state.modulePath,
|
|
222
|
+
storyName: state.storyName,
|
|
223
|
+
...(state.mode === 'docs' ? { view: 'docs' as const } : {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
case 'build-profiles':
|
|
227
|
+
return { t: 'static', id };
|
|
228
|
+
default:
|
|
229
|
+
// scene/game (managed by syncCenterDocuments), prefab/content-browser
|
|
230
|
+
// (no implementers persist yet).
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Snapshot the CURRENT live workspace state (exported for tests). */
|
|
236
|
+
export function snapshotWorkspaceState(api: Pick<DockviewApi, 'toJSON'>): PersistedWorkspaceState {
|
|
237
|
+
const chat = harnessChat();
|
|
238
|
+
const open: PersistedDocument[] = [];
|
|
239
|
+
for (const doc of openWorkspaceDocuments()) {
|
|
240
|
+
const persisted = persistableDocument(doc);
|
|
241
|
+
if (persisted) open.push(persisted);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
chat: { collapsed: chat.collapsed },
|
|
245
|
+
layout: api.toJSON(),
|
|
246
|
+
documents: { open, activeId: activeWorkspaceDocumentId() },
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function restoreAssetDocument(
|
|
251
|
+
store: WorkspaceStateStore,
|
|
252
|
+
doc: Extract<PersistedDocument, { t: 'asset' }>,
|
|
253
|
+
): Promise<void> {
|
|
254
|
+
// Existence-only probe (`projectFileExists`) — a deleted asset resolves
|
|
255
|
+
// false and is dropped silently. Deliberately NOT `readProjectTextFile`:
|
|
256
|
+
// that reads the whole asset body just to throw it away here.
|
|
257
|
+
if (!(await projectFileExists(doc.path))) return;
|
|
258
|
+
openAssetDocument(store, doc.path, doc.assetKind as AssetKind);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function restoreStoryDocument(
|
|
262
|
+
store: WorkspaceStateStore,
|
|
263
|
+
doc: Extract<PersistedDocument, { t: 'story' }>,
|
|
264
|
+
): void {
|
|
265
|
+
const module_ = getProjectStoryModules().find((m) => m.modulePath === doc.modulePath);
|
|
266
|
+
if (!module_ || !module_.ok) return;
|
|
267
|
+
if (!module_.stories.some((s) => s.name === doc.storyName)) return;
|
|
268
|
+
if (doc.view === 'docs') openStoryDocsDocument(store, doc.modulePath, doc.storyName);
|
|
269
|
+
else openStoryDocument(store, doc.modulePath, doc.storyName);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function restoreStaticDocument(store: WorkspaceStateStore, id: string): void {
|
|
273
|
+
if (id === LEGACY_PROFILER_DOCUMENT_ID) openDebuggerPanel('profiler');
|
|
274
|
+
else if (id === DATA_ASSETS_DOCUMENT_ID) openDataAssetsDocument(store);
|
|
275
|
+
else if (id === BUILD_PROFILES_DOCUMENT_ID && getEditorMode() === 'server')
|
|
276
|
+
openBuildProfilesDocument(store);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function restoreOneDocument(
|
|
280
|
+
store: WorkspaceStateStore,
|
|
281
|
+
doc: PersistedDocument,
|
|
282
|
+
): Promise<void> {
|
|
283
|
+
switch (doc.t) {
|
|
284
|
+
case 'asset':
|
|
285
|
+
return restoreAssetDocument(store, doc);
|
|
286
|
+
case 'data':
|
|
287
|
+
if (loadedDataFiles().some((f) => f.path === doc.path)) openDataDocument(store, doc.path);
|
|
288
|
+
return;
|
|
289
|
+
case 'tool':
|
|
290
|
+
openToolDocument(store, doc.toolId); // self-verifying (false = no-op)
|
|
291
|
+
return;
|
|
292
|
+
case 'story':
|
|
293
|
+
restoreStoryDocument(store, doc);
|
|
294
|
+
return;
|
|
295
|
+
case 'static':
|
|
296
|
+
restoreStaticDocument(store, doc.id);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Restore the persisted open documents + active document (exported for
|
|
303
|
+
* tests). Verification sources (data files / tools / stories) are awaited
|
|
304
|
+
* ONCE up front — only for the kinds actually present — so every open below
|
|
305
|
+
* checks a settled store, in the original tab order.
|
|
306
|
+
*/
|
|
307
|
+
export async function restoreWorkspaceDocuments(
|
|
308
|
+
store: WorkspaceStateStore,
|
|
309
|
+
persisted: PersistedWorkspaceState,
|
|
310
|
+
): Promise<void> {
|
|
311
|
+
const docs = persisted.documents;
|
|
312
|
+
if (!docs || docs.open.length === 0) {
|
|
313
|
+
// Still normalize the active document below when an activeId exists.
|
|
314
|
+
if (docs?.activeId) activateWorkspaceDocument(docs.activeId);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (docs.open.some((d) => d.t === 'data')) await refreshDataFiles().catch(() => {});
|
|
318
|
+
if (docs.open.some((d) => d.t === 'tool')) await refreshProjectTools().catch(() => {});
|
|
319
|
+
if (docs.open.some((d) => d.t === 'story')) {
|
|
320
|
+
const project = getCurrentProject();
|
|
321
|
+
if (project) await refreshProjectStories(project).catch(() => {});
|
|
322
|
+
}
|
|
323
|
+
for (const doc of docs.open) {
|
|
324
|
+
try {
|
|
325
|
+
await restoreOneDocument(store, doc);
|
|
326
|
+
} catch {
|
|
327
|
+
// Drop silently — a document that cannot truthfully reopen is gone.
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Restore the active tab. Opening activates as it goes, so end by
|
|
331
|
+
// activating the stored active id; if it did not survive restore (dropped,
|
|
332
|
+
// or it was the Game document), fall back to the scene document — there is
|
|
333
|
+
// always an active center document.
|
|
334
|
+
if (!docs.activeId || !activateWorkspaceDocument(docs.activeId)) {
|
|
335
|
+
activateWorkspaceDocument(SCENE_DOCUMENT_ID);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Install workspace-state persistence for this editor session:
|
|
341
|
+
* 1. restore open documents (async, verified — module header rules);
|
|
342
|
+
* 2. apply Dockview's serialized layout after those panel ids exist;
|
|
343
|
+
* 3. subscribe write-through (debounced 300ms, flushed on pagehide).
|
|
344
|
+
* Returns a teardown (unsubscribes; flushes a pending write).
|
|
345
|
+
*/
|
|
346
|
+
export function installWorkspaceStatePersistence(
|
|
347
|
+
store: WorkspaceStateStore,
|
|
348
|
+
api: DockviewApi,
|
|
349
|
+
reconcile: () => void,
|
|
350
|
+
): () => void {
|
|
351
|
+
const projectId = projectIdentity();
|
|
352
|
+
const persisted = readPersistedWorkspaceState(projectId);
|
|
353
|
+
if (persisted?.chat) hydrateHarnessChatLayout(persisted.chat);
|
|
354
|
+
|
|
355
|
+
let disposed = false;
|
|
356
|
+
let postLayoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
357
|
+
let layoutApplied = false;
|
|
358
|
+
const applyPersistedLayout = () => {
|
|
359
|
+
if (layoutApplied) return;
|
|
360
|
+
layoutApplied = true;
|
|
361
|
+
if (persisted?.layout) {
|
|
362
|
+
try {
|
|
363
|
+
api.fromJSON(persisted.layout);
|
|
364
|
+
} catch {
|
|
365
|
+
// A stale/corrupt convenience layout must never block the editor.
|
|
366
|
+
api.clear();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Restored layouts may omit capabilities added by a newer editor.
|
|
370
|
+
// Reconciliation adds only those missing panels and preserves every
|
|
371
|
+
// surviving group's user-selected position and size.
|
|
372
|
+
reconcile();
|
|
373
|
+
// Dockview completes part of `fromJSON` on its next render turn. A
|
|
374
|
+
// synchronous reconcile alone can therefore run before restored panel
|
|
375
|
+
// parameters are visible and leave a removed manifest-world tab behind.
|
|
376
|
+
// Reconcile once more after that turn so persisted geometry is retained
|
|
377
|
+
// while panels with no live document registration are dropped.
|
|
378
|
+
postLayoutTimer = setTimeout(() => {
|
|
379
|
+
postLayoutTimer = null;
|
|
380
|
+
if (disposed) return;
|
|
381
|
+
reconcile();
|
|
382
|
+
// `fromJSON` also restores Dockview's physical active panel. If that
|
|
383
|
+
// panel belonged to a world that no longer exists, removing it makes
|
|
384
|
+
// Dockview choose an arbitrary neighbor. The document registry is the
|
|
385
|
+
// semantic source of truth, so reassert its verified active document
|
|
386
|
+
// after stale-panel pruning.
|
|
387
|
+
const activeId = activeWorkspaceDocumentId();
|
|
388
|
+
if (activeId) api.getPanel(activeId)?.api.setActive();
|
|
389
|
+
}, 0);
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
393
|
+
const unsubscribers: Array<() => void> = [];
|
|
394
|
+
|
|
395
|
+
const flush = () => {
|
|
396
|
+
if (timer !== null) {
|
|
397
|
+
clearTimeout(timer);
|
|
398
|
+
timer = null;
|
|
399
|
+
}
|
|
400
|
+
writePersistedWorkspaceState(projectId, snapshotWorkspaceState(api));
|
|
401
|
+
};
|
|
402
|
+
const schedule = () => {
|
|
403
|
+
if (timer !== null) clearTimeout(timer);
|
|
404
|
+
timer = setTimeout(flush, WRITE_DEBOUNCE_MS);
|
|
405
|
+
};
|
|
406
|
+
const onPageHide = () => flush();
|
|
407
|
+
|
|
408
|
+
// Subscriptions install only after restore settles (module header: a
|
|
409
|
+
// half-restored open-set must never overwrite the stored one). A teardown
|
|
410
|
+
// racing the restore is honored via `disposed`. Bounded by
|
|
411
|
+
// `RESTORE_TIMEOUT_MS`: a hung verification fetch (a stalled asset-existence
|
|
412
|
+
// probe, a network partition on the story/tool/data refresh calls, etc.)
|
|
413
|
+
// must never leave the WHOLE session with no write-through and no
|
|
414
|
+
// pagehide-flush — layout/open-tab state is convenience state, availability
|
|
415
|
+
// beats completeness. Whichever settles first wins; if the restore is
|
|
416
|
+
// still running when the timeout fires, its later document-opens still
|
|
417
|
+
// land normally (the subscriptions below are just live earlier).
|
|
418
|
+
let restoreTimer: ReturnType<typeof setTimeout> | null = null;
|
|
419
|
+
const restoreSettled = (
|
|
420
|
+
persisted ? restoreWorkspaceDocuments(store, persisted) : Promise.resolve()
|
|
421
|
+
)
|
|
422
|
+
.catch(() => {})
|
|
423
|
+
.finally(() => {
|
|
424
|
+
if (disposed) return;
|
|
425
|
+
applyPersistedLayout();
|
|
426
|
+
reconcile();
|
|
427
|
+
});
|
|
428
|
+
const restoreTimedOut = new Promise<void>((resolve) => {
|
|
429
|
+
restoreTimer = setTimeout(() => {
|
|
430
|
+
applyPersistedLayout();
|
|
431
|
+
resolve();
|
|
432
|
+
}, RESTORE_TIMEOUT_MS);
|
|
433
|
+
});
|
|
434
|
+
const restorePromise = Promise.race([restoreSettled, restoreTimedOut]).then(() => {
|
|
435
|
+
if (restoreTimer !== null) clearTimeout(restoreTimer);
|
|
436
|
+
if (disposed) return;
|
|
437
|
+
const dockviewDisposable = api.onDidLayoutChange(schedule);
|
|
438
|
+
unsubscribers.push(
|
|
439
|
+
() => dockviewDisposable.dispose(),
|
|
440
|
+
subscribeHarnessChatLayout(schedule),
|
|
441
|
+
subscribeWorkspaceDocuments(schedule),
|
|
442
|
+
);
|
|
443
|
+
window.addEventListener('pagehide', onPageHide);
|
|
444
|
+
});
|
|
445
|
+
void restorePromise;
|
|
446
|
+
|
|
447
|
+
return () => {
|
|
448
|
+
disposed = true;
|
|
449
|
+
if (postLayoutTimer !== null) clearTimeout(postLayoutTimer);
|
|
450
|
+
if (restoreTimer !== null) clearTimeout(restoreTimer);
|
|
451
|
+
for (const unsub of unsubscribers) unsub();
|
|
452
|
+
window.removeEventListener('pagehide', onPageHide);
|
|
453
|
+
if (timer !== null) flush();
|
|
454
|
+
};
|
|
455
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace STATUS-CONTRIBUTION registry (W0,
|
|
3
|
+
* docs/EDITOR-WORKSPACE-SHELL-TODO.md §7 — the shell-contract slice of §9
|
|
4
|
+
* W0). "Status is status, not a command" (§1.5): save/dirty/failure state,
|
|
5
|
+
* problem counts, connection/play state, build progress, and concise
|
|
6
|
+
* selection information belong in a narrow status bar — not mixed into the
|
|
7
|
+
* global command header and not spread across viewport overlays.
|
|
8
|
+
*
|
|
9
|
+
* Consumed since W4 by `components/StatusBar.tsx` (rendered under the
|
|
10
|
+
* bottom dock/drawer strip); the core contributions live in
|
|
11
|
+
* `components/status-contributions.tsx` (+ `build-documents.tsx`'s
|
|
12
|
+
* `build-progress`). Migration map: docs/workspace-shell/W0-INVENTORY.md.
|
|
13
|
+
*
|
|
14
|
+
* Idioms mirror `inspector-section-registry.ts`: module-scope state,
|
|
15
|
+
* `useSyncExternalStore`-shaped subscribe + monotonic version,
|
|
16
|
+
* `__resetForTest`, pure-logic headless tests (`Content` is never rendered
|
|
17
|
+
* here). Ids key persisted/status-bar slots, so duplicates throw (same rule
|
|
18
|
+
* as `workspace-utility-registry.ts`).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ComponentType } from 'react';
|
|
22
|
+
|
|
23
|
+
/** The §7 status contribution contract: one compact status-bar item. */
|
|
24
|
+
export interface WorkspaceStatusContribution {
|
|
25
|
+
/** Stable id — keys the status-bar slot (`data-testid`). */
|
|
26
|
+
readonly id: string;
|
|
27
|
+
/** Which end of the bar (§4.1: session state left, selection info right).
|
|
28
|
+
* Default `'left'`. */
|
|
29
|
+
readonly align?: 'left' | 'right';
|
|
30
|
+
/** Slot order within its side (ascending; ties keep registration order).
|
|
31
|
+
* Default `0`. */
|
|
32
|
+
readonly order?: number;
|
|
33
|
+
/** React content host — compact, passive, non-command (§1.5). Never
|
|
34
|
+
* rendered by the registry itself. */
|
|
35
|
+
readonly Content: ComponentType;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Module-scope state (mirrors inspector-section-registry.ts) -----------
|
|
39
|
+
|
|
40
|
+
interface StoredContribution {
|
|
41
|
+
readonly contribution: WorkspaceStatusContribution;
|
|
42
|
+
readonly seq: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let _contributions: StoredContribution[] = [];
|
|
46
|
+
let _seq = 0;
|
|
47
|
+
let _version = 0;
|
|
48
|
+
const _listeners = new Set<() => void>();
|
|
49
|
+
|
|
50
|
+
function notifyChanged(): void {
|
|
51
|
+
_version++;
|
|
52
|
+
for (const fn of _listeners) fn();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Subscribe to (un)registrations. Returns an unsubscribe function. */
|
|
56
|
+
export function subscribeWorkspaceStatus(fn: () => void): () => void {
|
|
57
|
+
_listeners.add(fn);
|
|
58
|
+
return () => {
|
|
59
|
+
_listeners.delete(fn);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Monotonic change counter — the `getSnapshot` for `useSyncExternalStore`. */
|
|
64
|
+
export function workspaceStatusRegistryVersion(): number {
|
|
65
|
+
return _version;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Register a status contribution. Returns an unregister function.
|
|
70
|
+
* Throws on a duplicate id (ids key status-bar slots — same uniqueness rule
|
|
71
|
+
* as `registerWorkspaceUtility`).
|
|
72
|
+
*/
|
|
73
|
+
export function registerWorkspaceStatus(contribution: WorkspaceStatusContribution): () => void {
|
|
74
|
+
if (_contributions.some((c) => c.contribution.id === contribution.id)) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`registerWorkspaceStatus: id "${contribution.id}" is already registered — ` +
|
|
77
|
+
'status ids key status-bar slots, so they must be unique; unregister the ' +
|
|
78
|
+
'previous contribution first (docs/EDITOR-WORKSPACE-SHELL-TODO.md §7).',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const stored: StoredContribution = { contribution, seq: _seq++ };
|
|
82
|
+
_contributions = [..._contributions, stored];
|
|
83
|
+
notifyChanged();
|
|
84
|
+
return () => {
|
|
85
|
+
if (!_contributions.includes(stored)) return;
|
|
86
|
+
_contributions = _contributions.filter((c) => c !== stored);
|
|
87
|
+
notifyChanged();
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Registered contributions for one side of the bar, in (order,
|
|
92
|
+
* registration-sequence) order; with no argument, both sides (left first). */
|
|
93
|
+
export function workspaceStatusContributions(
|
|
94
|
+
align?: 'left' | 'right',
|
|
95
|
+
): WorkspaceStatusContribution[] {
|
|
96
|
+
const side = (want: 'left' | 'right') =>
|
|
97
|
+
[..._contributions]
|
|
98
|
+
.filter((c) => (c.contribution.align ?? 'left') === want)
|
|
99
|
+
.sort((a, b) => (a.contribution.order ?? 0) - (b.contribution.order ?? 0) || a.seq - b.seq)
|
|
100
|
+
.map((c) => c.contribution);
|
|
101
|
+
if (align) return side(align);
|
|
102
|
+
return [...side('left'), ...side('right')];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Test-only reset (mirrors `__resetInspectorSectionRegistryForTest`). */
|
|
106
|
+
export function __resetWorkspaceStatusRegistryForTest(): void {
|
|
107
|
+
_contributions = [];
|
|
108
|
+
_seq = 0;
|
|
109
|
+
notifyChanged();
|
|
110
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Semantic commands and failure auto-open policy for workspace utilities. */
|
|
2
|
+
|
|
3
|
+
import { getBuildSession, subscribeBuildSession } from './components/build-documents';
|
|
4
|
+
import { showWorkspaceUtility, toggleWorkspaceUtility } from './workspace-dock-controller';
|
|
5
|
+
|
|
6
|
+
export const CONSOLE_UTILITY_ID = 'console';
|
|
7
|
+
export const DEBUGGER_UTILITY_ID = 'debugger';
|
|
8
|
+
export const BUILD_OUTPUT_UTILITY_ID = 'build-output';
|
|
9
|
+
|
|
10
|
+
export interface UtilityAutoOpenStore {
|
|
11
|
+
subscribe(fn: () => void): () => void;
|
|
12
|
+
readonly saveState: 'saved' | 'unsaved' | 'failed';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Only actionable failures open a utility automatically. */
|
|
16
|
+
export function installUtilityAutoOpen(store: UtilityAutoOpenStore): () => void {
|
|
17
|
+
let previousSave = store.saveState;
|
|
18
|
+
const stopStore = store.subscribe(() => {
|
|
19
|
+
const next = store.saveState;
|
|
20
|
+
if (next === 'failed' && previousSave !== 'failed') showWorkspaceUtility(CONSOLE_UTILITY_ID);
|
|
21
|
+
previousSave = next;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let previousBuildPhase = getBuildSession().phase;
|
|
25
|
+
const stopBuild = subscribeBuildSession(() => {
|
|
26
|
+
const session = getBuildSession();
|
|
27
|
+
if (session.phase === 'done' && previousBuildPhase !== 'done' && session.result?.ok === false) {
|
|
28
|
+
showWorkspaceUtility(BUILD_OUTPUT_UTILITY_ID);
|
|
29
|
+
}
|
|
30
|
+
previousBuildPhase = session.phase;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
stopStore();
|
|
35
|
+
stopBuild();
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Legacy shell events remain stable while their physical implementation is Dockview. */
|
|
40
|
+
export function installUtilityEvents(): () => void {
|
|
41
|
+
const onToggleConsole = () => toggleWorkspaceUtility(CONSOLE_UTILITY_ID);
|
|
42
|
+
const onShowBuildOutput = () => showWorkspaceUtility(BUILD_OUTPUT_UTILITY_ID);
|
|
43
|
+
window.addEventListener('editor:toggle-console', onToggleConsole);
|
|
44
|
+
window.addEventListener('editor:show-build-output', onShowBuildOutput);
|
|
45
|
+
return () => {
|
|
46
|
+
window.removeEventListener('editor:toggle-console', onToggleConsole);
|
|
47
|
+
window.removeEventListener('editor:show-build-output', onShowBuildOutput);
|
|
48
|
+
};
|
|
49
|
+
}
|