@vgai/editor 0.2.0
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/LICENSE +661 -0
- package/README.md +35 -0
- package/dist/a/b/c/test-nested.json +1 -0
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
- package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
- package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
- package/dist/assets/browserAll-CEkWYlFk.js +1 -0
- package/dist/assets/collision-world-DniMB0qq.glb +0 -0
- package/dist/assets/create-runtime-DYIWr3va.js +1 -0
- package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
- package/dist/assets/esbuild-BHljloGq.wasm +0 -0
- package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
- package/dist/assets/game-8SDZPPgH.js +1 -0
- package/dist/assets/game-BI111o5f.js +10 -0
- package/dist/assets/game-CevorxyV.js +1 -0
- package/dist/assets/game-Di1H0gFj.js +1 -0
- package/dist/assets/game-Di7XlywD.js +1 -0
- package/dist/assets/game-DjnCaw8R.js +1 -0
- package/dist/assets/game-Q9A0aXSV.js +1 -0
- package/dist/assets/game-pt_1lQLv.js +1 -0
- package/dist/assets/game-source-CvidN7AB.js +1 -0
- package/dist/assets/game-xsOho_wg.js +83 -0
- package/dist/assets/index-Bp5cecD6.js +12 -0
- package/dist/assets/index-CPVV6pjw.js +1 -0
- package/dist/assets/index-CpOnYyeD.js +213 -0
- package/dist/assets/index-Cs8xwv3e.js +1 -0
- package/dist/assets/index-DL7RcQ0n.js +27 -0
- package/dist/assets/index-Dr8H_6Rl.js +1 -0
- package/dist/assets/index-_bCaipUk.js +13650 -0
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
- package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
- package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
- package/dist/assets/scene-query-BivAHhJu.js +1 -0
- package/dist/assets/stats.module--VATS4Kh.js +1 -0
- package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
- package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
- package/dist/index.html +37 -0
- package/dist/ingest/racing-game/UPSTREAM.md +68 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/index.css +1 -0
- package/dist/ingest/racing-game/images/bronze.png +0 -0
- package/dist/ingest/racing-game/images/gold.png +0 -0
- package/dist/ingest/racing-game/images/silver.png +0 -0
- package/dist/ingest/racing-game/index.html +15 -0
- package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
- package/dist/ingest/racing-game/models/track-draco.glb +0 -0
- package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
- package/dist/ingest/racing-game/rg-entry.js +1 -0
- package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
- package/dist/ingest/racing-game/textures/cursor.svg +3 -0
- package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
- package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
- package/dist/ingest/racing-game/textures/mask.svg +11 -0
- package/dist/ingest/racing-game/textures/no_sound.png +0 -0
- package/dist/ingest/racing-game/three-r139.module.js +50617 -0
- package/dist/ingest/racing-game/vendor.js +179 -0
- package/dist/ingest/racing-game/vgai.game.json +20 -0
- package/dist/ingest/simcity/UPSTREAM.md +65 -0
- package/dist/ingest/simcity/favicon.ico +0 -0
- package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
- package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
- package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
- package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
- package/dist/ingest/simcity/icons/bulldozer.png +0 -0
- package/dist/ingest/simcity/icons/calendar.png +0 -0
- package/dist/ingest/simcity/icons/factory-color.png +0 -0
- package/dist/ingest/simcity/icons/factory.png +0 -0
- package/dist/ingest/simcity/icons/house-color.png +0 -0
- package/dist/ingest/simcity/icons/house.png +0 -0
- package/dist/ingest/simcity/icons/job.png +0 -0
- package/dist/ingest/simcity/icons/office.png +0 -0
- package/dist/ingest/simcity/icons/pause-color.png +0 -0
- package/dist/ingest/simcity/icons/pause.png +0 -0
- package/dist/ingest/simcity/icons/person.png +0 -0
- package/dist/ingest/simcity/icons/play-color.png +0 -0
- package/dist/ingest/simcity/icons/play.png +0 -0
- package/dist/ingest/simcity/icons/power-color.png +0 -0
- package/dist/ingest/simcity/icons/power-line-color.png +0 -0
- package/dist/ingest/simcity/icons/power.png +0 -0
- package/dist/ingest/simcity/icons/road-color.png +0 -0
- package/dist/ingest/simcity/icons/road.png +0 -0
- package/dist/ingest/simcity/icons/select-color.png +0 -0
- package/dist/ingest/simcity/icons/select.png +0 -0
- package/dist/ingest/simcity/icons/store-color.png +0 -0
- package/dist/ingest/simcity/index.html +94 -0
- package/dist/ingest/simcity/index.js +58 -0
- package/dist/ingest/simcity/main.css +233 -0
- package/dist/ingest/simcity/models/armored-truck.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
- package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
- package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
- package/dist/ingest/simcity/models/building-cafe.glb +0 -0
- package/dist/ingest/simcity/models/building-casino.glb +0 -0
- package/dist/ingest/simcity/models/building-cinema.glb +0 -0
- package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
- package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
- package/dist/ingest/simcity/models/building-office-big.glb +0 -0
- package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
- package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
- package/dist/ingest/simcity/models/building-office.glb +0 -0
- package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
- package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
- package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
- package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
- package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger.glb +0 -0
- package/dist/ingest/simcity/models/car-police.glb +0 -0
- package/dist/ingest/simcity/models/car-taxi.glb +0 -0
- package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
- package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
- package/dist/ingest/simcity/models/car-veteran.glb +0 -0
- package/dist/ingest/simcity/models/construction-small.glb +0 -0
- package/dist/ingest/simcity/models/data-center.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory.glb +0 -0
- package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
- package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
- package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
- package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
- package/dist/ingest/simcity/models/truck.glb +0 -0
- package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
- package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
- package/dist/ingest/simcity/textures/base.png +0 -0
- package/dist/ingest/simcity/textures/grid.png +0 -0
- package/dist/ingest/simcity/textures/specular.png +0 -0
- package/dist/ingest/simcity/three-r155.module.js +51466 -0
- package/dist/ingest/simcity/vgai.game.json +20 -0
- package/dist/ingest/tanks/UPSTREAM.md +69 -0
- package/dist/ingest/tanks/index.html +214 -0
- package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
- package/dist/ingest/tanks/tanks-entry.js +92 -0
- package/dist/ingest/tanks/three-r170.module.js +54571 -0
- package/dist/ingest/tanks/vgai.game.json +20 -0
- package/dist/inputmaps/default.inputmap.json +41 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/dist/scenes/untitled.vscn.json +29 -0
- package/dist/textures/waternormals.jpg +0 -0
- package/dist-server/packaged.mjs +18155 -0
- package/package.json +36 -0
- package/server/asset-library-routes.ts +833 -0
- package/server/canonical-path.ts +50 -0
- package/server/cloud-asset-catalog.ts +101 -0
- package/server/dev.ts +350 -0
- package/server/editor-server.ts +1537 -0
- package/server/editor-sse.ts +73 -0
- package/server/game-globals-shadow.ts +58 -0
- package/server/local-asset-catalog.ts +163 -0
- package/server/packaged.ts +571 -0
- package/server/prod.ts +75 -0
- package/server/project-optimize-deps-entries.ts +149 -0
- package/server/project-view.ts +65 -0
- package/server/server-utils.ts +430 -0
- package/server/session-registry.ts +93 -0
- package/src/EditorContext.tsx +87 -0
- package/src/achieved-tier.ts +126 -0
- package/src/action-registry.ts +268 -0
- package/src/adapter-resolver.ts +1858 -0
- package/src/asset-events.ts +100 -0
- package/src/authoring/active-adapter.ts +98 -0
- package/src/authoring/active-systems.ts +50 -0
- package/src/authoring/boundary-authoring-adapter.ts +178 -0
- package/src/authoring/composite-authoring-adapter.ts +891 -0
- package/src/authoring/design-time-layers.ts +776 -0
- package/src/authoring/edit-mode-authoring.ts +643 -0
- package/src/authoring/embed-only-adapter.ts +57 -0
- package/src/authoring/ephemeral-persistence.ts +26 -0
- package/src/authoring/eyedropper-session.ts +69 -0
- package/src/authoring/first-party-hierarchy-menu.ts +74 -0
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
- package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
- package/src/authoring/first-party-inspector/register.tsx +108 -0
- package/src/authoring/ingest-authoring-adapter.ts +475 -0
- package/src/authoring/ingest-capture.ts +94 -0
- package/src/authoring/ingest-game-adapter.ts +291 -0
- package/src/authoring/ingest-iframe-adapter.ts +38 -0
- package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
- package/src/authoring/ingest-overlay.ts +193 -0
- package/src/authoring/layered-pick.ts +87 -0
- package/src/authoring/mount-failure-report.ts +112 -0
- package/src/authoring/overlay-report.ts +52 -0
- package/src/authoring/project-scenes.ts +111 -0
- package/src/authoring/react-dom-authoring-adapter.ts +795 -0
- package/src/authoring/react-inspector-section.tsx +1041 -0
- package/src/authoring/react-world-authoring-adapter.ts +2400 -0
- package/src/authoring/root-activation.ts +61 -0
- package/src/authoring/scene-ui-layer.ts +485 -0
- package/src/authoring/story-overlay.ts +48 -0
- package/src/authoring/ui-authoring-adapter.ts +325 -0
- package/src/authoring/ui-hierarchy-menu.ts +40 -0
- package/src/authoring/ui-inspector-sections.tsx +156 -0
- package/src/authoring/undo-timeline.ts +145 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
- package/src/authoring/viewport-pick-context.ts +31 -0
- package/src/authoring/viewport-raycast.ts +80 -0
- package/src/authoring/world-overlay.ts +76 -0
- package/src/authoring/world-pan-state.ts +115 -0
- package/src/authoring/world-session-state.ts +102 -0
- package/src/authoring/world2d-authoring-adapter.ts +313 -0
- package/src/browser-external-watch.ts +98 -0
- package/src/browser-play-entry.ts +112 -0
- package/src/browser-project-scripts.ts +112 -0
- package/src/browser-transpile.ts +246 -0
- package/src/browser-watch-reload.ts +100 -0
- package/src/command-listener.ts +326 -0
- package/src/components/AlignToolbar.tsx +189 -0
- package/src/components/AppRoot.tsx +73 -0
- package/src/components/AssetBrowser.tsx +502 -0
- package/src/components/BottomPanel.tsx +326 -0
- package/src/components/BuildPanel.tsx +455 -0
- package/src/components/CameraInfo.tsx +56 -0
- package/src/components/CommandPalette.tsx +265 -0
- package/src/components/ConsolePanel.tsx +272 -0
- package/src/components/DataPanel.tsx +1268 -0
- package/src/components/DefaultEditorLayout.tsx +509 -0
- package/src/components/FolderTree.tsx +179 -0
- package/src/components/GameHierarchy.tsx +1349 -0
- package/src/components/Inspector.tsx +671 -0
- package/src/components/InspectorToolSection.tsx +74 -0
- package/src/components/MountFailureBanner.tsx +110 -0
- package/src/components/NetworkSection.tsx +109 -0
- package/src/components/OnlineAssetBrowser.tsx +448 -0
- package/src/components/OverlayReportBanner.tsx +122 -0
- package/src/components/PlayBar.tsx +356 -0
- package/src/components/PrefabSaveDialog.tsx +201 -0
- package/src/components/ProjectHeader.tsx +174 -0
- package/src/components/ProjectScreen.tsx +1004 -0
- package/src/components/RightPanel.tsx +166 -0
- package/src/components/RootActivateDialog.tsx +105 -0
- package/src/components/SaveDialog.tsx +197 -0
- package/src/components/SaveStatus.tsx +69 -0
- package/src/components/StatsOverlay.tsx +35 -0
- package/src/components/ToolHost.tsx +80 -0
- package/src/components/Toolbar.tsx +307 -0
- package/src/components/ViewportOverlay.tsx +234 -0
- package/src/components/ViewportPanel.tsx +369 -0
- package/src/components/WorldContextMenu.tsx +257 -0
- package/src/components/WorldSelectionOverlay.tsx +2003 -0
- package/src/components/WorldTextEditor.tsx +86 -0
- package/src/components/asset-viewers/AssetViewer.tsx +38 -0
- package/src/components/asset-viewers/AudioViewer.tsx +149 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
- package/src/components/asset-viewers/ImageViewer.tsx +65 -0
- package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
- package/src/components/asset-viewers/ModelViewer.tsx +180 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
- package/src/components/data-csv.ts +301 -0
- package/src/components/entity-name-commit.ts +18 -0
- package/src/components/inspector-property-grouping.ts +65 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
- package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
- package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
- package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/components/inspector-widgets/FontPicker.tsx +196 -0
- package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
- package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
- package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
- package/src/components/inspector-widgets/color-utils.ts +202 -0
- package/src/components/inspector-widgets/gallery-main.tsx +167 -0
- package/src/components/inspector-widgets/index.ts +22 -0
- package/src/components/inspector-widgets/shared.tsx +135 -0
- package/src/components/inspectors/AnimationSection.tsx +143 -0
- package/src/components/inspectors/AudioSection.tsx +215 -0
- package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
- package/src/components/inspectors/CameraSection.tsx +105 -0
- package/src/components/inspectors/ComponentsSection.tsx +358 -0
- package/src/components/inspectors/JointsSection.tsx +255 -0
- package/src/components/inspectors/LightSection.tsx +116 -0
- package/src/components/inspectors/MaterialSection.tsx +1189 -0
- package/src/components/inspectors/MeshSection.tsx +99 -0
- package/src/components/inspectors/NavigationSection.tsx +58 -0
- package/src/components/inspectors/ParticleSection.tsx +681 -0
- package/src/components/inspectors/PhysicsSection.tsx +326 -0
- package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
- package/src/components/inspectors/RenderingSection.tsx +109 -0
- package/src/components/inspectors/ShadowSection.tsx +84 -0
- package/src/components/inspectors/SplineSection.tsx +191 -0
- package/src/components/inspectors/TransformSection.tsx +191 -0
- package/src/components/inspectors/json-schema-fields.ts +281 -0
- package/src/components/inspectors/multi-edit-utils.ts +49 -0
- package/src/components/inspectors/zod-fields.ts +184 -0
- package/src/components/primitives/ColorInput.tsx +109 -0
- package/src/components/primitives/JsonInput.tsx +98 -0
- package/src/components/primitives/NumberInput.tsx +164 -0
- package/src/components/primitives/Panel.tsx +68 -0
- package/src/components/primitives/SectionHeader.tsx +45 -0
- package/src/components/primitives/Tooltip.tsx +82 -0
- package/src/components/primitives/Vec3Input.tsx +62 -0
- package/src/components/world-overlay-gestures.ts +1438 -0
- package/src/doctor/data-checks.ts +285 -0
- package/src/doctor/report.ts +386 -0
- package/src/editor-api.ts +700 -0
- package/src/editor-console.ts +194 -0
- package/src/editor-hotkeys.ts +441 -0
- package/src/editor-layers.ts +2 -0
- package/src/editor-mode.ts +28 -0
- package/src/editor-store.ts +2225 -0
- package/src/editor-viewport.ts +1924 -0
- package/src/entity-factory.ts +221 -0
- package/src/game-globals-prelude.ts +27 -0
- package/src/gated-globals.ts +131 -0
- package/src/generated/setup-by-example.generated.ts +53 -0
- package/src/gizmo-registry.ts +608 -0
- package/src/handle-system.ts +221 -0
- package/src/hierarchy-menu-registry.ts +70 -0
- package/src/hierarchy-rows.ts +73 -0
- package/src/hosted-example.ts +160 -0
- package/src/hotkeys.ts +142 -0
- package/src/ingest/discovery-react.ts +132 -0
- package/src/ingest/discovery.ts +205 -0
- package/src/ingest/discovery2d.ts +204 -0
- package/src/ingest/esm-entry-resolution.ts +102 -0
- package/src/ingest/games/bloom-composer/game.js +99 -0
- package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
- package/src/ingest/games/embed-bundle/embed.html +10 -0
- package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
- package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
- package/src/ingest/games/games-fps/game.js +479 -0
- package/src/ingest/games/games-fps/vgai.game.json +24 -0
- package/src/ingest/games/iframe-reachable/game-source.js +58 -0
- package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
- package/src/ingest/games/interactive-cubes/game.js +139 -0
- package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
- package/src/ingest/games/mismatched-three/fake-three.js +38 -0
- package/src/ingest/games/mismatched-three/game.js +10 -0
- package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
- package/src/ingest/games/own-three-bundled/game.js +43 -0
- package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
- package/src/ingest/games/pointerlock-maze/game.js +288 -0
- package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
- package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
- package/src/ingest/games-2d/bunnymark/game.ts +88 -0
- package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy/game.ts +148 -0
- package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
- package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
- package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
- package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
- package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
- package/src/ingest/host-namespace-registry.ts +196 -0
- package/src/ingest/types.ts +127 -0
- package/src/ingest-hierarchy-rows.ts +207 -0
- package/src/ingest-mode.ts +2092 -0
- package/src/ingest-siblings.ts +466 -0
- package/src/initial-scene.ts +194 -0
- package/src/inspector-section-registry.ts +117 -0
- package/src/interactive-edit-scope.ts +34 -0
- package/src/main.tsx +23 -0
- package/src/manifest-project.ts +43 -0
- package/src/model-thumbnail.ts +156 -0
- package/src/module-mode.ts +513 -0
- package/src/navmesh-handler.ts +94 -0
- package/src/packaged-runtime.ts +49 -0
- package/src/play-mode.ts +1148 -0
- package/src/playground/main.ts +206 -0
- package/src/project-manager.ts +173 -0
- package/src/scene-diff.ts +112 -0
- package/src/scene-serializer.ts +144 -0
- package/src/scene-sync.ts +587 -0
- package/src/scene-ui-mode/active-scene-ui.ts +58 -0
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
- package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
- package/src/script-loader.ts +143 -0
- package/src/storage/fsa-storage.ts +55 -0
- package/src/storage/handle-storage.ts +150 -0
- package/src/storage/handle-store.ts +116 -0
- package/src/storage/http-storage.ts +137 -0
- package/src/storage/index.ts +147 -0
- package/src/storage/mem-storage.ts +135 -0
- package/src/storage/opfs-storage.ts +25 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/seed.ts +106 -0
- package/src/storage/types.ts +110 -0
- package/src/tool-loader.ts +291 -0
- package/src/ui-editor/inspector.tsx +563 -0
- package/src/ui-editor/node-ops.ts +266 -0
- package/src/ui-editor/overlay.tsx +380 -0
- package/src/ui-editor/palette.tsx +114 -0
- package/src/ui-editor/react-store.ts +78 -0
- package/src/ui-editor/timeline-panel.tsx +443 -0
- package/src/ui-editor/ui-edit-store.ts +552 -0
- package/src/ui-editor/ui-prefab.ts +95 -0
- package/src/ui-source/inspect.ts +544 -0
- package/src/ui-source/oid-transform.ts +133 -0
- package/src/ui-source/source-write-backend.ts +203 -0
- package/src/ui-source/tailwind-map.ts +174 -0
- package/src/ui-source/writer.ts +1175 -0
- package/src/widgets/index.ts +73 -0
- package/template/.claude/skills/editor/SKILL.md +252 -0
- package/template/CLAUDE.md +512 -0
- package/template/LICENSE +30 -0
- package/template/emit-schemas.ts +42 -0
- package/template/index.html +17 -0
- package/template/package.json +53 -0
- package/template/project.json +9 -0
- package/template/public/audio/ambient-arena.wav +0 -0
- package/template/public/audio/ambient-music.wav +0 -0
- package/template/public/audio/ambient-nature.wav +0 -0
- package/template/public/audio/ambient-wind.wav +0 -0
- package/template/public/inputmaps/default.inputmap.json +41 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/template/public/scenes/default.vscn.json +43 -0
- package/template/public/scenes/untitled.vscn.json +29 -0
- package/template/public/textures/waternormals.jpg +0 -0
- package/template/server/colyseus-setup.ts +85 -0
- package/template/server/main.ts +34 -0
- package/template/server/rooms/arena-room.ts +420 -0
- package/template/server/rooms/game-room.ts +162 -0
- package/template/server/rooms.ts +15 -0
- package/template/server/tsconfig.json +19 -0
- package/template/src/data/README.md +40 -0
- package/template/src/data/assets.ts +31 -0
- package/template/src/data/tuning.data.json +5 -0
- package/template/src/data/tuning.schema.json +21 -0
- package/template/src/data/tuning.schema.ts +34 -0
- package/template/src/data/tuning.ts +24 -0
- package/template/src/main.ts +157 -0
- package/template/src/scripts/components/data-spinner.ts +45 -0
- package/template/src/scripts/components/scene-camera.ts +40 -0
- package/template/src/scripts/main.ts +94 -0
- package/template/src/scripts/registry.ts +46 -0
- package/template/src/tools/README.md +14 -0
- package/template/src/tools/example.tool.tsx +91 -0
- package/template/src/tools/spin.tool.tsx +101 -0
- package/template/src/ui/Button.tsx +27 -0
- package/template/src/ui/DefaultHud.tsx +40 -0
- package/template/src/ui/KeyHint.tsx +25 -0
- package/template/src/ui/Panel.tsx +52 -0
- package/template/src/ui/RebindPanel.tsx +129 -0
- package/template/src/ui/Slider.tsx +28 -0
- package/template/src/ui/StatusDot.tsx +22 -0
- package/template/src/ui/Toggle.tsx +19 -0
- package/template/src/ui/game-state.tsx +37 -0
- package/template/src/ui/index.ts +7 -0
- package/template/src/ui/mount-default-hud.tsx +19 -0
- package/template/src/ui/mount-ui.ts +15 -0
- package/template/tsconfig.json +36 -0
- package/template/validate-asset-content.ts +78 -0
- package/template/validate-manifest.ts +55 -0
- package/template/validate-scenes.ts +211 -0
- package/template/vgai.game.json +21 -0
- package/template/vite.config.ts +128 -0
- package/vite-plugin-example-assets.ts +60 -0
- package/vite-plugin-game-globals.ts +46 -0
- package/vite-plugin-game-static.ts +71 -0
- package/vite-plugin-project-game-static.ts +84 -0
- package/vite-plugin-react-game-static.ts +98 -0
- package/vite-plugin-react-world-provider.ts +81 -0
- package/vite-plugin-react-world-runtime.ts +76 -0
- package/vite-plugin-ui-oid.ts +561 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin: live OID instrumentation + source-write endpoints (C1/C2/C7/C8).
|
|
3
|
+
*
|
|
4
|
+
* The visual-edit "trick" made live for hand-authored React UI components: at dev
|
|
5
|
+
* time it stamps `data-oid` on every JSX element in files matched by `include` (a
|
|
6
|
+
* predicate, so the rest of the editor build is untouched), builds an OID→source
|
|
7
|
+
* index, and serves:
|
|
8
|
+
* GET /__ui-source/index → the whole OID index
|
|
9
|
+
* POST /__ui-source/write {oid,prop,value} → surgical inline-style edit to source
|
|
10
|
+
* POST /__ui-source/struct {oid,op} → structural source edit (delete)
|
|
11
|
+
* Edits run the SAME `writer.ts` used in unit tests, server-side, then Vite HMR
|
|
12
|
+
* reloads. `data-oid` exists only in transform output, never on disk.
|
|
13
|
+
*
|
|
14
|
+
* `include` widening (T6.2 slice 2, `docs/REACT-WORLD-DESIGN.md` §1.D): the original
|
|
15
|
+
* scope was ONLY `ui-editor/editable-components/*.tsx` (the visual-edit fixture dir,
|
|
16
|
+
* for `UIAuthoringAdapter`/`SourceEditPanel`). A react WORLD's entry graph is a real
|
|
17
|
+
* project's `.tsx` source — the mounted template project (`packages/editor/template/
|
|
18
|
+
* src/**`) or an externally-scaffolded project directory reached via the SAME
|
|
19
|
+
* dev-server `fs.allow` pipeline `server/dev.ts` already builds for project scripts
|
|
20
|
+
* (T3.3) — so `ReactWorldAuthoringAdapter` needs those files instrumented too.
|
|
21
|
+
* `defaultProjectScopeInclude` widens to PROJECT scope, not repo-global: any `.tsx`
|
|
22
|
+
* file outside `node_modules`, EXCEPT the vgai tooling/engine source trees this repo
|
|
23
|
+
* itself is built from (`packages/engine/src`, `packages/create-vgai-project`,
|
|
24
|
+
* `packages/vgai-cli`, `packages/editor-sdk`, and `packages/editor/src` generally —
|
|
25
|
+
* carving OUT `ui-editor/editable-components` so the existing UI-edit-mode surface
|
|
26
|
+
* keeps working unchanged). This does NOT depend on which project happens to be
|
|
27
|
+
* open — `fs.allow` already bounds what Vite can even reach, and this predicate
|
|
28
|
+
* additionally keeps the vgai app's OWN react source un-instrumented.
|
|
29
|
+
*
|
|
30
|
+
* Vendored-tree exclusion (Track N, D-N4 item 2 — docs/REACT-INGEST-LANDING-
|
|
31
|
+
* DESIGN.md): any path under a `/vendor/` segment (`vendor/pixi-games/…`,
|
|
32
|
+
* and — once N2 lands — `vendor/react-games/…`) is REJECTED regardless of
|
|
33
|
+
* extension. Vendored trees hold unmodified, foreign game source (Track P's
|
|
34
|
+
* pixi games today; Track N's react games starting N2) — the whole point of
|
|
35
|
+
* vendoring is a byte-for-byte, never-modified tree (`verify-unaltered.mjs`'s
|
|
36
|
+
* zero-diff CI gate). `data-oid` stamping is what makes a `.tsx` file
|
|
37
|
+
* WRITABLE through `/__ui-source/write` (`ReactWorldAuthoringAdapter`'s
|
|
38
|
+
* source-write path, D-N4 item 1's companion) — stamping a vendored file
|
|
39
|
+
* would silently reopen the never-modify-game-source rule for it. Today this
|
|
40
|
+
* is a no-op in practice (the existing `.tsx`-only filter already misses
|
|
41
|
+
* `vendor/pixi-games/`'s `.jsx` sources, and react-rpg — the first vendored
|
|
42
|
+
* react game, D-N5 — is ALSO `.jsx`), but it is closing a real future hole,
|
|
43
|
+
* not changing current behavior: the day a vendored `.tsx` game lands (or an
|
|
44
|
+
* existing one is ever renamed), this is the only thing standing between it
|
|
45
|
+
* and becoming editable.
|
|
46
|
+
*
|
|
47
|
+
* D-Y4 (wave 2, docs/EXTERNAL-REACT-AND-MODULE-DESIGN.md §1/§4 slice S3) —
|
|
48
|
+
* the write-back ban becomes IDENTITY-scoped, not just location-scoped: the
|
|
49
|
+
* `/vendor/` regex above only protects the repo's OWN vendored trees, but
|
|
50
|
+
* wave 2 lets an `ingest-react` world live in ANY external, user-owned
|
|
51
|
+
* folder (`vgai edit <folder>`) — an ingested-as-is foreign game there would
|
|
52
|
+
* otherwise be silently stamped/writable, reopening the never-modify-game-
|
|
53
|
+
* source rule by location rather than by identity. `nearestManifestExcludesIngestReact`
|
|
54
|
+
* walks up from a candidate `.tsx`'s own directory to the NEAREST
|
|
55
|
+
* `vgai.game.json`; if that manifest declares an `{ ingest }` react world,
|
|
56
|
+
* the whole manifest folder's subtree is excluded — exact, not coarse:
|
|
57
|
+
* `ingest-react` is single-world by construction (`adapter-resolver.ts`'s
|
|
58
|
+
* `MULTI_WORLD_ALLOWED_IDENTITIES` exclusion), so "the manifest folder" IS
|
|
59
|
+
* the foreign game's whole tree, and D-N8 ships no authoring for it anyway —
|
|
60
|
+
* excluding it from stamping loses nothing. Cached per directory (hit AND
|
|
61
|
+
* miss, R-Y3) so a project with no manifest anywhere up the tree pays the
|
|
62
|
+
* walk cost once, ever. Malformed JSON at the manifest path is treated as
|
|
63
|
+
* "no exclusion" (never crash the transform hook over a broken file); a
|
|
64
|
+
* genuine fs error OTHER than "not found" (e.g. a permission error) is not
|
|
65
|
+
* swallowed — it propagates, same honesty bar the rest of this file holds.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
import { createHash } from 'node:crypto';
|
|
69
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
70
|
+
import { dirname, join } from 'node:path';
|
|
71
|
+
import type { Plugin } from 'vite';
|
|
72
|
+
import { lineColToOffset, OidStore, transformSource } from './src/ui-source/oid-transform';
|
|
73
|
+
import {
|
|
74
|
+
deleteElement,
|
|
75
|
+
deleteElements,
|
|
76
|
+
duplicateElement,
|
|
77
|
+
editTextContent,
|
|
78
|
+
getEditableText,
|
|
79
|
+
insertChildElement,
|
|
80
|
+
removeInlineStyle,
|
|
81
|
+
reorderChild,
|
|
82
|
+
reparentElement,
|
|
83
|
+
type StructEditResult,
|
|
84
|
+
type StyleEditResult,
|
|
85
|
+
surgicalCssEdit,
|
|
86
|
+
unwrapElement,
|
|
87
|
+
wrapElement,
|
|
88
|
+
writePropChange,
|
|
89
|
+
writeStyleAuto,
|
|
90
|
+
} from './src/ui-source/writer';
|
|
91
|
+
|
|
92
|
+
const NODE_MODULES_RE = /\/node_modules\//;
|
|
93
|
+
const TOOLING_SRC_RE = /\/packages\/(engine\/src|create-vgai-project|vgai-cli|editor-sdk)\//;
|
|
94
|
+
const EDITOR_SRC_RE = /\/packages\/editor\/src\//;
|
|
95
|
+
const EDITABLE_COMPONENTS_RE = /\/ui-editor\/editable-components\/.*\.tsx$/;
|
|
96
|
+
/** Track N, D-N4 item 2 — see the module doc comment's "Vendored-tree exclusion". */
|
|
97
|
+
const VENDOR_RE = /\/vendor\//;
|
|
98
|
+
|
|
99
|
+
/** True if a parsed `vgai.game.json` body declares at least one
|
|
100
|
+
* `kind: 'react'` world with an `{ ingest }` adapter (kind-agnostic on
|
|
101
|
+
* `strategy` — every ingest-react strategy is D-N8 no-authoring today, and
|
|
102
|
+
* a future strategy should not silently reopen the hole). Defensive against
|
|
103
|
+
* any shape (never trusts the manifest is even an object) — this is a
|
|
104
|
+
* best-effort identity probe for a WRITE-BACK BAN, not manifest validation
|
|
105
|
+
* (the real Zod schema/loader is `@engine/manifest/load`, not reachable
|
|
106
|
+
* from this vite-config-time, dependency-light file by design). */
|
|
107
|
+
function manifestDeclaresIngestReactWorld(parsed: unknown): boolean {
|
|
108
|
+
if (!parsed || typeof parsed !== 'object') return false;
|
|
109
|
+
const worlds = (parsed as { worlds?: unknown }).worlds;
|
|
110
|
+
if (!Array.isArray(worlds)) return false;
|
|
111
|
+
return worlds.some((entry) => {
|
|
112
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
113
|
+
const world = entry as { kind?: unknown; adapter?: unknown };
|
|
114
|
+
if (world.kind !== 'react') return false;
|
|
115
|
+
const { adapter } = world;
|
|
116
|
+
return !!adapter && typeof adapter === 'object' && 'ingest' in adapter;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Cache: absolute directory -> whether ITS OWN ancestor walk (starting AT
|
|
121
|
+
* this directory) hit an ingest-react-declaring manifest. Cached both ways
|
|
122
|
+
* (hit and miss, R-Y3) — see the module doc comment's D-Y4 section. */
|
|
123
|
+
const manifestAncestorExclusionCache = new Map<string, boolean>();
|
|
124
|
+
|
|
125
|
+
function nearestManifestExcludesIngestReact(dir: string): boolean {
|
|
126
|
+
const cached = manifestAncestorExclusionCache.get(dir);
|
|
127
|
+
if (cached !== undefined) return cached;
|
|
128
|
+
|
|
129
|
+
const manifestPath = join(dir, 'vgai.game.json');
|
|
130
|
+
let raw: string | undefined;
|
|
131
|
+
try {
|
|
132
|
+
raw = readFileSync(manifestPath, 'utf8');
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// ENOENT (no manifest at this level) -> keep walking up. Any OTHER fs
|
|
135
|
+
// error (e.g. EACCES) is a real environment problem, not "no manifest
|
|
136
|
+
// here" — never silently swallowed.
|
|
137
|
+
if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let result: boolean;
|
|
141
|
+
if (raw === undefined) {
|
|
142
|
+
const parent = dirname(dir);
|
|
143
|
+
result = parent === dir ? false : nearestManifestExcludesIngestReact(parent);
|
|
144
|
+
} else {
|
|
145
|
+
try {
|
|
146
|
+
result = manifestDeclaresIngestReactWorld(JSON.parse(raw));
|
|
147
|
+
} catch {
|
|
148
|
+
// Malformed JSON: honest "no exclusion" (stamping stays on) — never
|
|
149
|
+
// crash the transform hook over a broken manifest file.
|
|
150
|
+
result = false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
manifestAncestorExclusionCache.set(dir, result);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Cap 2 (React visual-edit parity): the scope guard for the `/__ui-source/css` endpoint.
|
|
159
|
+
* A CSS file path (from a stylesheet's `data-vite-dev-id`) is editable iff it is a
|
|
160
|
+
* first-party PROJECT `.css` file — same carve-outs as `defaultProjectScopeInclude`
|
|
161
|
+
* (never node_modules, vendored trees, the vgai tooling/engine source, or the editor's
|
|
162
|
+
* own source). Paths are normalized to forward slashes so a Windows dev-id matches. This
|
|
163
|
+
* is the CSS analogue of the `.tsx` stamping scope: an out-of-scope path must not become
|
|
164
|
+
* writable through a client-supplied `file`.
|
|
165
|
+
*/
|
|
166
|
+
export function isEditableCssFile(id: string): boolean {
|
|
167
|
+
const clean = (id.split('?')[0] ?? id).replace(/\\/g, '/');
|
|
168
|
+
if (!clean.endsWith('.css')) return false;
|
|
169
|
+
if (NODE_MODULES_RE.test(clean)) return false;
|
|
170
|
+
if (VENDOR_RE.test(clean)) return false;
|
|
171
|
+
if (TOOLING_SRC_RE.test(clean)) return false;
|
|
172
|
+
if (EDITOR_SRC_RE.test(clean)) return false;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** D-1 (Phase D, spec27 §2): sha256 hex digest — the checksum guard the
|
|
177
|
+
* `/__ui-source/restore` endpoint (below) uses to refuse a stale write. Only
|
|
178
|
+
* ever computed SERVER-SIDE: the client (`ReactWorldAuthoringAdapter`) never
|
|
179
|
+
* hashes anything itself, it only echoes back a digest this server already
|
|
180
|
+
* handed it in a prior `writeStruct`/`restoreSource` response. */
|
|
181
|
+
function sha256(text: string): string {
|
|
182
|
+
return createHash('sha256').update(text, 'utf8').digest('hex');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Default `include` predicate (see the module doc comment above for the reasoning). */
|
|
186
|
+
export function defaultProjectScopeInclude(id: string): boolean {
|
|
187
|
+
if (!id.endsWith('.tsx')) return false;
|
|
188
|
+
if (NODE_MODULES_RE.test(id)) return false;
|
|
189
|
+
if (VENDOR_RE.test(id)) return false;
|
|
190
|
+
if (TOOLING_SRC_RE.test(id)) return false;
|
|
191
|
+
if (EDITOR_SRC_RE.test(id) && !EDITABLE_COMPONENTS_RE.test(id)) return false;
|
|
192
|
+
if (nearestManifestExcludesIngestReact(dirname(id))) return false;
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* D-A3 (wave 13, `docs/WAVE13-JSX-SPREAD-UNDO-DESIGN.md`): route a
|
|
198
|
+
* `/__ui-source/write` body to the write-or-remove writer call — a `value: null`
|
|
199
|
+
* body is the REMOVAL sentinel (paired with `SourceWriteBackend.removeStyle`),
|
|
200
|
+
* routing to `removeInlineStyle`; any other value routes to the normal
|
|
201
|
+
* write-or-append path (`writeStyleAuto`, unchanged). Exported as a pure function
|
|
202
|
+
* so this routing decision is unit-testable without spinning up a dev server —
|
|
203
|
+
* the OID→offset resolution and the actual fs read/write around it stay inline in
|
|
204
|
+
* the middleware below (real I/O, not worth mocking).
|
|
205
|
+
*/
|
|
206
|
+
export function applyStyleWriteRequest(
|
|
207
|
+
code: string,
|
|
208
|
+
elementStart: number,
|
|
209
|
+
prop: string,
|
|
210
|
+
value: string | number | null,
|
|
211
|
+
): StyleEditResult & { route?: 'class' | 'inline' } {
|
|
212
|
+
if (value === null) return removeInlineStyle(code, elementStart, prop);
|
|
213
|
+
return writeStyleAuto(code, elementStart, prop, value);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The JSON response a source-endpoint handler produces (status defaults to 200). */
|
|
217
|
+
export interface HandlerResult {
|
|
218
|
+
status?: number;
|
|
219
|
+
body: unknown;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** POST `/__ui-source/write` — surgical inline-style/className edit (F5 routing).
|
|
223
|
+
* D-A3 (wave 13): a `value: null` body is the REMOVAL sentinel — routed via
|
|
224
|
+
* `applyStyleWriteRequest` to `removeInlineStyle` (append-aware undo); `appended`
|
|
225
|
+
* is forwarded so the client's undo can remove vs. restore. */
|
|
226
|
+
function handleWrite(store: OidStore, body: Record<string, unknown>): HandlerResult {
|
|
227
|
+
const { oid, prop, value } = body as { oid: string; prop: string; value: string | null };
|
|
228
|
+
const entry = store.index.get(oid);
|
|
229
|
+
if (!entry) return { body: { changed: false, error: 'unknown oid' } };
|
|
230
|
+
const src = readFileSync(entry.file, 'utf8');
|
|
231
|
+
const off = lineColToOffset(src, entry.line, entry.col);
|
|
232
|
+
const result = applyStyleWriteRequest(src, off, prop, value);
|
|
233
|
+
if (result.changed) writeFileSync(entry.file, result.code);
|
|
234
|
+
return {
|
|
235
|
+
body: {
|
|
236
|
+
changed: result.changed,
|
|
237
|
+
dynamic: result.dynamic,
|
|
238
|
+
route: result.route,
|
|
239
|
+
appended: result.appended,
|
|
240
|
+
file: entry.file,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* POST `/__ui-source/css` — Cap 2 surgical CSS-FILE edit. The client (adapter) resolves
|
|
247
|
+
* the target rule from live matched-rule data (`inspect.ts` getMatchedCssRules) and sends
|
|
248
|
+
* the rule's source file + selector; this edits that file in place. The file path is
|
|
249
|
+
* client-supplied, so it MUST pass the first-party CSS scope guard before any read/write
|
|
250
|
+
* — an out-of-scope path is rejected, not touched. A selector not present in source is
|
|
251
|
+
* generated CSS (`generated: true`); the client re-routes to class/inline.
|
|
252
|
+
*
|
|
253
|
+
* Exported (unlike its sibling handlers above, which close over the live
|
|
254
|
+
* `OidStore`) so it's directly unit-testable without a dev server — it's pure
|
|
255
|
+
* body-in/HandlerResult-out plus real fs I/O on a client-supplied, scope-
|
|
256
|
+
* guarded path, no store/session state involved.
|
|
257
|
+
*/
|
|
258
|
+
export function handleCss(body: Record<string, unknown>): HandlerResult {
|
|
259
|
+
const { file, selector, prop, value } = body as {
|
|
260
|
+
file: string;
|
|
261
|
+
selector: string;
|
|
262
|
+
prop: string;
|
|
263
|
+
value: string;
|
|
264
|
+
};
|
|
265
|
+
if (!file || !isEditableCssFile(file)) {
|
|
266
|
+
return { body: { changed: false, error: 'css file out of editable scope' } };
|
|
267
|
+
}
|
|
268
|
+
const cleanFile = file.split('?')[0] ?? file;
|
|
269
|
+
const src = readFileSync(cleanFile, 'utf8');
|
|
270
|
+
const edited = surgicalCssEdit(src, selector, prop, value);
|
|
271
|
+
if (edited === null) return { body: { changed: false, generated: true, file: cleanFile } };
|
|
272
|
+
if (edited !== src) writeFileSync(cleanFile, edited);
|
|
273
|
+
return { body: { changed: edited !== src, file: cleanFile } };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* POST `/__ui-source/text` — Cap 3 text-content edit. Replaces a leaf element's pure-text
|
|
278
|
+
* body in place (guarded: refuses a body with `{expression}` or child elements). Returns
|
|
279
|
+
* `prevText` (the editable text before the edit) for the client's undo inverse.
|
|
280
|
+
*/
|
|
281
|
+
function handleText(store: OidStore, body: Record<string, unknown>): HandlerResult {
|
|
282
|
+
const { oid, text } = body as { oid: string; text: string };
|
|
283
|
+
const entry = store.index.get(oid);
|
|
284
|
+
if (!entry) return { body: { changed: false, error: 'unknown oid' } };
|
|
285
|
+
const src = readFileSync(entry.file, 'utf8');
|
|
286
|
+
const off = lineColToOffset(src, entry.line, entry.col);
|
|
287
|
+
const prevText = getEditableText(src, off);
|
|
288
|
+
const result = editTextContent(src, off, text);
|
|
289
|
+
if (result.changed) writeFileSync(entry.file, result.code);
|
|
290
|
+
return {
|
|
291
|
+
body: { changed: result.changed, dynamic: result.dynamic, prevText, file: entry.file },
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* POST `/__ui-source/prop` — Cap 4 component prop / attribute edit at the call site. The
|
|
297
|
+
* client sends the CALL-SITE oid (resolved from the fiber, `inspect.ts` getCallSiteOid)
|
|
298
|
+
* whose index entry points at the `<Component …>` tag. Guards a dynamic (non-literal) prop
|
|
299
|
+
* and the event-handler / structural props.
|
|
300
|
+
*/
|
|
301
|
+
function handleProp(store: OidStore, body: Record<string, unknown>): HandlerResult {
|
|
302
|
+
const { oid, prop, value } = body as { oid: string; prop: string; value: string };
|
|
303
|
+
const entry = store.index.get(oid);
|
|
304
|
+
if (!entry) return { body: { changed: false, error: 'unknown oid' } };
|
|
305
|
+
const src = readFileSync(entry.file, 'utf8');
|
|
306
|
+
const off = lineColToOffset(src, entry.line, entry.col);
|
|
307
|
+
const result = writePropChange(src, off, prop, value);
|
|
308
|
+
if (result.changed) writeFileSync(entry.file, result.code);
|
|
309
|
+
return { body: { changed: result.changed, dynamic: result.dynamic, file: entry.file } };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* POST `/__ui-source/struct` — Cap 5 structural source edits. Ops: `delete`, `duplicate`,
|
|
314
|
+
* `wrap` (+wrapperTag), `unwrap`, `create` (insert a `<tag/>` child of `oid`), `reorder`
|
|
315
|
+
* (move `oid` before `targetOid`, or to the end of `parentOid`), and `reparent` (move `oid`
|
|
316
|
+
* into `parentOid`). Multi-oid ops resolve every oid to an offset in the SAME file (a
|
|
317
|
+
* cross-file move is refused) — OID-resolved, immune to index races.
|
|
318
|
+
*
|
|
319
|
+
* D-1 (Phase D, spec27 §2 "known limitation to schedule, not hide"): when the op actually
|
|
320
|
+
* changes the file, the response also carries the WHOLE file's `prevSource`/`newSource`
|
|
321
|
+
* (before/after this write) and their `prevSha`/`newSha` (sha256 digests, computed here —
|
|
322
|
+
* the client never hashes anything itself). `ReactWorldAuthoringAdapter.structOp` uses
|
|
323
|
+
* these to push a checksum-guarded whole-file-snapshot undo/redo entry — the sound inverse
|
|
324
|
+
* for a structural op (a per-OID inverse is unsound: OIDs are content-signature keyed,
|
|
325
|
+
* reorder/delete reassign occurrence indices, and delete has no inverse payload). Exported
|
|
326
|
+
* (like `handleCss`) so it's directly unit-testable without a running dev server.
|
|
327
|
+
*/
|
|
328
|
+
export function handleStruct(store: OidStore, body: Record<string, unknown>): HandlerResult {
|
|
329
|
+
const { oid, op, targetOid, parentOid, wrapperTag } = body as {
|
|
330
|
+
oid: string;
|
|
331
|
+
op: string;
|
|
332
|
+
targetOid?: string;
|
|
333
|
+
parentOid?: string;
|
|
334
|
+
wrapperTag?: string;
|
|
335
|
+
};
|
|
336
|
+
const entry = store.index.get(oid);
|
|
337
|
+
if (!entry) return { body: { changed: false, error: 'unknown oid' } };
|
|
338
|
+
const src = readFileSync(entry.file, 'utf8');
|
|
339
|
+
const off = lineColToOffset(src, entry.line, entry.col);
|
|
340
|
+
// Resolve a companion oid to an offset in the SAME file, or null (cross-file ⇒ error).
|
|
341
|
+
const resolve = (id: string | undefined): number | null | 'cross-file' => {
|
|
342
|
+
if (!id) return null;
|
|
343
|
+
const e = store.index.get(id);
|
|
344
|
+
if (!e) return null;
|
|
345
|
+
if (e.file !== entry.file) return 'cross-file';
|
|
346
|
+
return lineColToOffset(src, e.line, e.col);
|
|
347
|
+
};
|
|
348
|
+
const target = resolve(targetOid);
|
|
349
|
+
const parent = resolve(parentOid);
|
|
350
|
+
if (target === 'cross-file' || parent === 'cross-file') {
|
|
351
|
+
return { body: { changed: false, error: 'cannot move across files' } };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let result: StructEditResult;
|
|
355
|
+
switch (op) {
|
|
356
|
+
case 'delete':
|
|
357
|
+
result = deleteElement(src, off);
|
|
358
|
+
break;
|
|
359
|
+
case 'duplicate':
|
|
360
|
+
result = duplicateElement(src, off);
|
|
361
|
+
break;
|
|
362
|
+
case 'wrap':
|
|
363
|
+
result = wrapElement(src, off, wrapperTag ?? 'div');
|
|
364
|
+
break;
|
|
365
|
+
case 'unwrap':
|
|
366
|
+
result = unwrapElement(src, off);
|
|
367
|
+
break;
|
|
368
|
+
case 'create':
|
|
369
|
+
result = insertChildElement(src, off, wrapperTag ?? 'div');
|
|
370
|
+
break;
|
|
371
|
+
case 'reorder':
|
|
372
|
+
result = reorderChild(src, off, target, parent);
|
|
373
|
+
break;
|
|
374
|
+
case 'reparent':
|
|
375
|
+
result = parent == null ? { code: src, changed: false } : reparentElement(src, off, parent);
|
|
376
|
+
break;
|
|
377
|
+
default:
|
|
378
|
+
return { body: { changed: false, error: `unknown struct op: ${op}` } };
|
|
379
|
+
}
|
|
380
|
+
if (!result.changed) return { body: { changed: false, file: entry.file } };
|
|
381
|
+
const prevSource = src;
|
|
382
|
+
const newSource = result.code;
|
|
383
|
+
writeFileSync(entry.file, newSource);
|
|
384
|
+
return {
|
|
385
|
+
body: {
|
|
386
|
+
changed: true,
|
|
387
|
+
file: entry.file,
|
|
388
|
+
prevSource,
|
|
389
|
+
newSource,
|
|
390
|
+
prevSha: sha256(prevSource),
|
|
391
|
+
newSha: sha256(newSource),
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* POST `/__ui-source/struct-many` — delete-order-residual fix (bug-panel follow-up
|
|
398
|
+
* to the multi-delete corruption fix, 50f90a6d). Delete-only (today's one caller,
|
|
399
|
+
* `ReactWorldAuthoringAdapter.structure.removeMany`): resolves EVERY `oid` in `oids`
|
|
400
|
+
* to an offset against ONE `readFileSync` snapshot of their (shared) file, then
|
|
401
|
+
* applies all of them via `deleteElements` (`writer.ts`) — highest offset first, so
|
|
402
|
+
* no still-queued target's offset is ever shifted out from under it, REGARDLESS of
|
|
403
|
+
* what order the caller listed `oids` in. This is what makes the batch immune to the
|
|
404
|
+
* delete-order-residual: `deleteSelection` (`editor-hotkeys.ts`)'s per-id fallback
|
|
405
|
+
* loop derives its delete order from the CALLER's own hierarchy walk (DOM order for
|
|
406
|
+
* `ReactWorldAuthoringAdapter` — see `walkOidTree`), which can diverge from true
|
|
407
|
+
* SOURCE order when a component's live DOM child order is a runtime permutation of
|
|
408
|
+
* its JSX (e.g. `{[...els].reverse()}`); this endpoint never consults that walk order
|
|
409
|
+
* at all, only each oid's own registered `{line, col}` and ONE shared file read, so
|
|
410
|
+
* caller order is irrelevant to correctness. Explicitly NOT a re-scan/re-transform of
|
|
411
|
+
* the file per oid (see `deleteElements`'s doc comment for why that would be UNSOUND
|
|
412
|
+
* here — a live re-transform mid-batch reassigns occurrence-index-based oid identity
|
|
413
|
+
* out from under the very oids this batch is still trying to resolve). One write, one
|
|
414
|
+
* `prevSource`/`newSource` pair — one undo entry for the whole batch (a bonus over
|
|
415
|
+
* the N-entry per-id fallback, not the point of this fix, but free from reusing
|
|
416
|
+
* `pushStructUndo`'s existing whole-file-snapshot inverse unchanged).
|
|
417
|
+
*/
|
|
418
|
+
export function handleStructMany(store: OidStore, body: Record<string, unknown>): HandlerResult {
|
|
419
|
+
const { oids, op } = body as { oids?: unknown; op?: string };
|
|
420
|
+
if (op !== 'delete') return { body: { changed: false, error: `unsupported batch op: ${op}` } };
|
|
421
|
+
if (!Array.isArray(oids) || oids.length === 0) {
|
|
422
|
+
return { body: { changed: false, error: 'oids must be a non-empty array' } };
|
|
423
|
+
}
|
|
424
|
+
const uniqueOids = [...new Set(oids as string[])];
|
|
425
|
+
const entries = uniqueOids.map((oid) => ({ oid, entry: store.index.get(oid) }));
|
|
426
|
+
const missing = entries.find((e) => !e.entry);
|
|
427
|
+
if (missing) return { body: { changed: false, error: `unknown oid: ${missing.oid}` } };
|
|
428
|
+
const file = entries[0]!.entry!.file;
|
|
429
|
+
const crossFile = entries.find((e) => e.entry!.file !== file);
|
|
430
|
+
if (crossFile) return { body: { changed: false, error: 'cannot batch-delete across files' } };
|
|
431
|
+
|
|
432
|
+
const src = readFileSync(file, 'utf8'); // ONE snapshot every offset below resolves against
|
|
433
|
+
const offsets = entries.map((e) => lineColToOffset(src, e.entry!.line, e.entry!.col));
|
|
434
|
+
const result = deleteElements(src, offsets);
|
|
435
|
+
if (!result.changed) return { body: { changed: false, file } };
|
|
436
|
+
const prevSource = src;
|
|
437
|
+
const newSource = result.code;
|
|
438
|
+
writeFileSync(file, newSource);
|
|
439
|
+
return {
|
|
440
|
+
body: {
|
|
441
|
+
changed: true,
|
|
442
|
+
file,
|
|
443
|
+
prevSource,
|
|
444
|
+
newSource,
|
|
445
|
+
prevSha: sha256(prevSource),
|
|
446
|
+
newSha: sha256(newSource),
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* POST `/__ui-source/restore` — D-1 (Phase D, spec27 §2): the checksum-guarded whole-file
|
|
453
|
+
* restore that makes Cap-5 structural edits (delete/duplicate/wrap/unwrap/reorder/reparent/
|
|
454
|
+
* create) undoable. `ReactWorldAuthoringAdapter.structOp` captures `{prevSource, newSource,
|
|
455
|
+
* prevSha, newSha}` from a struct write's response (`handleStruct`, above) and, on undo/
|
|
456
|
+
* redo, posts the INVERSE `{file, source, ifMatchSha}` here: undo sends `source: prevSource,
|
|
457
|
+
* ifMatchSha: newSha` (the hash of what SHOULD be on disk right now); redo sends the mirror
|
|
458
|
+
* image. Refuses — WITHOUT writing — when the file's CURRENT sha256 doesn't match
|
|
459
|
+
* `ifMatchSha` (an out-of-band change since: a hand-edit, a since-diverged reload, …);
|
|
460
|
+
* `restored: false` is the caller's cue to surface a loud refusal instead of silently
|
|
461
|
+
* corrupting the file. Scope-guarded the same way `handleCss`'s client-supplied `file` is
|
|
462
|
+
* (`defaultProjectScopeInclude` — restore targets the SAME `.tsx` project files struct ops
|
|
463
|
+
* do). Exported (like `handleCss`) so it's directly unit-testable without a dev server.
|
|
464
|
+
*/
|
|
465
|
+
export function handleRestore(body: Record<string, unknown>): HandlerResult {
|
|
466
|
+
const { file, source, ifMatchSha } = body as {
|
|
467
|
+
file: string;
|
|
468
|
+
source: string;
|
|
469
|
+
ifMatchSha: string;
|
|
470
|
+
};
|
|
471
|
+
if (!file || !defaultProjectScopeInclude(file)) {
|
|
472
|
+
return { status: 403, body: { restored: false, error: 'file out of editable scope' } };
|
|
473
|
+
}
|
|
474
|
+
let current: string;
|
|
475
|
+
try {
|
|
476
|
+
current = readFileSync(file, 'utf8');
|
|
477
|
+
} catch (e) {
|
|
478
|
+
return { status: 404, body: { restored: false, error: String(e) } };
|
|
479
|
+
}
|
|
480
|
+
const currentSha = sha256(current);
|
|
481
|
+
if (currentSha !== ifMatchSha) {
|
|
482
|
+
return {
|
|
483
|
+
status: 409,
|
|
484
|
+
body: {
|
|
485
|
+
restored: false,
|
|
486
|
+
error: 'stale: the file changed since this undo/redo entry was recorded',
|
|
487
|
+
sha: currentSha,
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
writeFileSync(file, source);
|
|
492
|
+
return { body: { restored: true, sha: sha256(source) } };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function uiOidPlugin(
|
|
496
|
+
include: RegExp | ((id: string) => boolean) = defaultProjectScopeInclude,
|
|
497
|
+
): Plugin {
|
|
498
|
+
const test = typeof include === 'function' ? include : (id: string) => include.test(id);
|
|
499
|
+
const store = new OidStore();
|
|
500
|
+
return {
|
|
501
|
+
name: 'vgai-ui-oid',
|
|
502
|
+
enforce: 'pre',
|
|
503
|
+
transform(code, id) {
|
|
504
|
+
const clean = id.split('?')[0] ?? id;
|
|
505
|
+
if (!test(clean)) return null;
|
|
506
|
+
const { code: out } = transformSource(code, clean, store);
|
|
507
|
+
return { code: out, map: null };
|
|
508
|
+
},
|
|
509
|
+
configureServer(server) {
|
|
510
|
+
// The Express host may apply express.json() app-wide, consuming the body and
|
|
511
|
+
// leaving the parsed object on req.body. Prefer that; else read the raw stream.
|
|
512
|
+
const readJson = async (
|
|
513
|
+
req: import('node:http').IncomingMessage,
|
|
514
|
+
): Promise<Record<string, unknown>> => {
|
|
515
|
+
const pre = (req as unknown as { body?: unknown }).body;
|
|
516
|
+
if (pre && typeof pre === 'object') return pre as Record<string, unknown>;
|
|
517
|
+
const chunks: Buffer[] = [];
|
|
518
|
+
for await (const chunk of req) chunks.push(chunk as Buffer);
|
|
519
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
520
|
+
return raw ? JSON.parse(raw) : {};
|
|
521
|
+
};
|
|
522
|
+
const json = (res: import('node:http').ServerResponse, obj: unknown, code = 200): void => {
|
|
523
|
+
res.statusCode = code;
|
|
524
|
+
res.setHeader('Content-Type', 'application/json');
|
|
525
|
+
res.end(JSON.stringify(obj));
|
|
526
|
+
};
|
|
527
|
+
// Single middleware switching on the URL — avoids connect prefix-matching subtleties.
|
|
528
|
+
// Each POST endpoint delegates to a small module-level handler (below) so this
|
|
529
|
+
// dispatcher stays flat; the try/catch + JSON plumbing lives here once.
|
|
530
|
+
const post = async (
|
|
531
|
+
req: import('node:http').IncomingMessage,
|
|
532
|
+
res: import('node:http').ServerResponse,
|
|
533
|
+
handler: (body: Record<string, unknown>) => HandlerResult,
|
|
534
|
+
): Promise<void> => {
|
|
535
|
+
try {
|
|
536
|
+
const result = handler(await readJson(req));
|
|
537
|
+
json(res, result.body, result.status ?? 200);
|
|
538
|
+
} catch (e) {
|
|
539
|
+
json(res, { changed: false, error: String(e) }, 400);
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
// POST route table — keeps the dispatcher flat (one lookup, no per-endpoint branch).
|
|
543
|
+
const postRoutes: Record<string, (body: Record<string, unknown>) => HandlerResult> = {
|
|
544
|
+
'/__ui-source/write': (b) => handleWrite(store, b),
|
|
545
|
+
'/__ui-source/css': handleCss,
|
|
546
|
+
'/__ui-source/text': (b) => handleText(store, b),
|
|
547
|
+
'/__ui-source/prop': (b) => handleProp(store, b),
|
|
548
|
+
'/__ui-source/struct': (b) => handleStruct(store, b),
|
|
549
|
+
'/__ui-source/struct-many': (b) => handleStructMany(store, b),
|
|
550
|
+
'/__ui-source/restore': handleRestore,
|
|
551
|
+
};
|
|
552
|
+
server.middlewares.use(async (req, res, next) => {
|
|
553
|
+
const url = (req.url ?? '').split('?')[0] ?? '';
|
|
554
|
+
if (url === '/__ui-source/index') return json(res, Object.fromEntries(store.index));
|
|
555
|
+
const handler = req.method === 'POST' ? postRoutes[url] : undefined;
|
|
556
|
+
if (handler) return post(req, res, handler);
|
|
557
|
+
next();
|
|
558
|
+
});
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|