@volter/editor-game 0.5.65
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/NOTICE +23 -0
- package/contributions/asset-budget-asset.menu.ts +26 -0
- package/contributions/asset-budget.action.ts +18 -0
- package/contributions/asset-budget.document.tsx +21 -0
- package/contributions/asset-budget.menu.ts +22 -0
- package/contributions/audio-unlock.service.ts +17 -0
- package/contributions/audio.utility.tsx +15 -0
- package/contributions/autoplay.service.ts +48 -0
- package/contributions/bridge.command.ts +172 -0
- package/contributions/build-profiles.document.tsx +21 -0
- package/contributions/build-progress.status.tsx +45 -0
- package/contributions/build.action.ts +26 -0
- package/contributions/build.command.ts +27 -0
- package/contributions/build.header.tsx +37 -0
- package/contributions/build.menu.ts +31 -0
- package/contributions/build.service.ts +31 -0
- package/contributions/connection.status.tsx +43 -0
- package/contributions/coverage.service.ts +118 -0
- package/contributions/edit-mode-audio.service.ts +23 -0
- package/contributions/edit-mode-networking.service.ts +30 -0
- package/contributions/game-document.service.ts +25 -0
- package/contributions/game-eval.command.ts +210 -0
- package/contributions/game.layout.ts +19 -0
- package/contributions/gameplay.command.ts +255 -0
- package/contributions/generation.service.ts +96 -0
- package/contributions/generations.status.tsx +53 -0
- package/contributions/ingest.service.ts +80 -0
- package/contributions/instances.command.ts +75 -0
- package/contributions/navmesh.menu.ts +39 -0
- package/contributions/navmesh.service.ts +19 -0
- package/contributions/network.utility.tsx +17 -0
- package/contributions/play.command.ts +355 -0
- package/contributions/profiler.action.ts +16 -0
- package/contributions/profiler.menu.ts +22 -0
- package/contributions/profiler.utility.tsx +17 -0
- package/contributions/react/component-board.service.ts +21 -0
- package/contributions/react/design-time-mount.service.ts +60 -0
- package/contributions/react/pasteboard.action.ts +41 -0
- package/contributions/react/react-inspector.service.ts +85 -0
- package/contributions/react/story-documents.service.ts +44 -0
- package/contributions/scene-document.service.ts +32 -0
- package/contributions/state-watch.action.ts +17 -0
- package/contributions/state-watch.menu.ts +23 -0
- package/contributions/state-watch.utility.tsx +20 -0
- package/contributions/team-playtest.service.ts +124 -0
- package/contributions/three/camera-runtime.inspector.tsx +34 -0
- package/contributions/three/component-board.service.ts +24 -0
- package/contributions/three/component-verbs.command.ts +110 -0
- package/contributions/three/component-verbs.service.ts +92 -0
- package/contributions/three/constraints.inspector.tsx +34 -0
- package/contributions/three/model-asset-sections.service.ts +26 -0
- package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
- package/contributions/three/story-documents.service.ts +31 -0
- package/contributions/three/three-authoring.service.ts +66 -0
- package/contributions/transport.header.tsx +19 -0
- package/contributions/xstate-behavior.action.ts +42 -0
- package/contributions/xstate-behavior.document.tsx +73 -0
- package/contributions/xstate-behavior.inspector.tsx +28 -0
- package/contributions/xstate-behavior.menu.ts +23 -0
- package/package.json +144 -0
- package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
- package/src/asset-budget/asset-budget-model.ts +799 -0
- package/src/asset-budget/basis-encoder.ts +165 -0
- package/src/asset-budget/gltf-io.ts +154 -0
- package/src/asset-budget/gltf-optimize.ts +384 -0
- package/src/asset-budget/image-dims.ts +78 -0
- package/src/asset-budget/optimize-apply.ts +221 -0
- package/src/audio/AudioDebuggerPanel.tsx +457 -0
- package/src/audio/audio-debugger-model.ts +87 -0
- package/src/bridge/call.ts +60 -0
- package/src/bridge/dispatch.ts +459 -0
- package/src/bridge/live-frames.ts +25 -0
- package/src/bridge/screenshot.ts +316 -0
- package/src/build/BuildProfilesPanel.tsx +476 -0
- package/src/build/build-session.ts +247 -0
- package/src/build/format-bytes.ts +14 -0
- package/src/command-results.ts +36 -0
- package/src/coverage/live-authoring-surface.ts +30 -0
- package/src/coverage/live-project-verbs.ts +162 -0
- package/src/coverage/native-system-coverage.ts +143 -0
- package/src/coverage/root-coverage.ts +79 -0
- package/src/coverage/session-coverage.ts +193 -0
- package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
- package/src/edit-mode/edit-mode-audio.ts +56 -0
- package/src/edit-mode/edit-mode-networking.ts +112 -0
- package/src/game-document/DevicePresetPicker.tsx +84 -0
- package/src/game-document/GameCaptureFrameButton.tsx +45 -0
- package/src/game-document/GameDocument.tsx +215 -0
- package/src/game-document/GamePanel.tsx +662 -0
- package/src/game-document/InstanceInspectorPicker.tsx +183 -0
- package/src/game-document/crowd-debug.ts +183 -0
- package/src/game-document/device-preview.ts +336 -0
- package/src/game-document/game-view-store.ts +107 -0
- package/src/game-document/physics-debug.ts +187 -0
- package/src/generation/GenerationActivity.tsx +421 -0
- package/src/generation/GenerationGallery.css +231 -0
- package/src/generation/generation-documents.tsx +338 -0
- package/src/generation/generation-jobs.ts +128 -0
- package/src/generation/generation-presentation.ts +257 -0
- package/src/host/adapter-reach.ts +445 -0
- package/src/host/adapter-runtime-bindings.ts +303 -0
- package/src/host/after-paint.ts +112 -0
- package/src/host/api/configurations.ts +66 -0
- package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
- package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
- package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
- package/src/host/authoring/contract-scenes-stories.ts +204 -0
- package/src/host/authoring/creation-site-related.ts +55 -0
- package/src/host/authoring/ephemeral-persistence.ts +29 -0
- package/src/host/authoring/gesture-persist.ts +84 -0
- package/src/host/authoring/ingest-data-writer.ts +232 -0
- package/src/host/authoring/ingest-source-persistence.ts +826 -0
- package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
- package/src/host/authoring/mounted-authoring.ts +41 -0
- package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
- package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
- package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
- package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
- package/src/host/authoring/pixi-isolation-assets.ts +25 -0
- package/src/host/authoring/pixi-live-write-target.ts +979 -0
- package/src/host/authoring/pixi-source-identity.ts +141 -0
- package/src/host/authoring/pixi-still-presentation.ts +71 -0
- package/src/host/authoring/pixi-structure-history.ts +237 -0
- package/src/host/authoring/pixi-transform-channels.ts +205 -0
- package/src/host/authoring/selection-remount-handoff.ts +23 -0
- package/src/host/authoring/source-persistence-backend.ts +373 -0
- package/src/host/authoring/source-refresh-revisions.ts +81 -0
- package/src/host/authoring/struct-write-pipe.ts +143 -0
- package/src/host/auto-frame-window.ts +89 -0
- package/src/host/binding-resolver.ts +393 -0
- package/src/host/browser-transpile.ts +631 -0
- package/src/host/canvas-entry-runtime.ts +95 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
- package/src/host/components/HeaderTelemetry.tsx +341 -0
- package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
- package/src/host/components/ResolutionPicker.tsx +89 -0
- package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
- package/src/host/components/frame-debugger-model.ts +579 -0
- package/src/host/components/header-telemetry-model.ts +74 -0
- package/src/host/components/scene-document.tsx +447 -0
- package/src/host/components/utility-view-state.ts +87 -0
- package/src/host/components/world-root-stage-binding.tsx +133 -0
- package/src/host/components/world-root-stage.ts +1046 -0
- package/src/host/coverage/authoring-read-probe.ts +583 -0
- package/src/host/coverage/capability-coverage.ts +1587 -0
- package/src/host/coverage/coverage-accounting.ts +261 -0
- package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
- package/src/host/coverage/project-verb-coverage.ts +148 -0
- package/src/host/coverage/system-adapter-coverage.ts +373 -0
- package/src/host/design-system-stories/StoryLayout.tsx +104 -0
- package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
- package/src/host/document-preview-three.ts +110 -0
- package/src/host/entry-adjudication.ts +89 -0
- package/src/host/game-location-guard.ts +150 -0
- package/src/host/game-module-access.ts +196 -0
- package/src/host/game-realm-page.ts +358 -0
- package/src/host/game-realm-reclaim.ts +55 -0
- package/src/host/game-realm-storage.ts +104 -0
- package/src/host/gameplay-export.ts +288 -0
- package/src/host/gameplay-recording.ts +717 -0
- package/src/host/gated-globals.ts +1511 -0
- package/src/host/history/json-history-resource.ts +254 -0
- package/src/host/ingest/registry.ts +261 -0
- package/src/host/instance-extract-actions.ts +120 -0
- package/src/host/instance-fork-actions.ts +120 -0
- package/src/host/play-control-hook.ts +26 -0
- package/src/host/playwright-shim.ts +479 -0
- package/src/host/projection/dom.ts +295 -0
- package/src/host/projection/pixi.ts +288 -0
- package/src/host/r3f-entry-runtime.ts +77 -0
- package/src/host/react-mount-runtime.ts +162 -0
- package/src/host/realm-services.ts +148 -0
- package/src/host/recording-preview.ts +106 -0
- package/src/host/roots/module-root.ts +203 -0
- package/src/host/roots/react-root.ts +181 -0
- package/src/host/same-realm-loop-gate.ts +544 -0
- package/src/host/scene-view-drawability.ts +69 -0
- package/src/host/sdk/tools.ts +31 -0
- package/src/host/served-bundle-runtime-modules.ts +331 -0
- package/src/host/server-log-bridge.ts +60 -0
- package/src/host/staged-projects.ts +24 -0
- package/src/host/stories/mounted-story-viewport-source.ts +64 -0
- package/src/host/stories/story-arg-descriptors.ts +50 -0
- package/src/host/stories/story-media-presence.ts +152 -0
- package/src/host/surface-content.ts +87 -0
- package/src/host/take-named-export.ts +23 -0
- package/src/host/three-ingest-runtime.ts +76 -0
- package/src/host/types-fastnoise-lite.d.ts +7 -0
- package/src/host/types-mikktspace.d.ts +20 -0
- package/src/host/types-troika-three-text.d.ts +7 -0
- package/src/host/use-active-performance-source.ts +53 -0
- package/src/host/viewport-pose-memory.ts +48 -0
- package/src/host/viewport-root-presentation.ts +40 -0
- package/src/ingest/active-ingest.ts +251 -0
- package/src/ingest/active-scene-navigation.ts +43 -0
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
- package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
- package/src/ingest/capture-wait-report.ts +122 -0
- package/src/ingest/deferred-ingest-play.ts +216 -0
- package/src/ingest/deferred-ingest-session.ts +23 -0
- package/src/ingest/discovery-public-ingest.ts +242 -0
- package/src/ingest/dom-stub-mark.ts +7 -0
- package/src/ingest/entry-load.ts +56 -0
- package/src/ingest/game-contract-realm.ts +34 -0
- package/src/ingest/game-pointer-lock.ts +89 -0
- package/src/ingest/held-scene-repaint.ts +73 -0
- package/src/ingest/host-surface-box.ts +174 -0
- package/src/ingest/ingest-boot-viewport.ts +54 -0
- package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
- package/src/ingest/ingest-canvas-scene.ts +50 -0
- package/src/ingest/ingest-evidence-hook.ts +131 -0
- package/src/ingest/ingest-frame-snapshot.ts +183 -0
- package/src/ingest/ingest-play-commands.ts +134 -0
- package/src/ingest/ingest-play-control.ts +299 -0
- package/src/ingest/ingest-render-debug.ts +320 -0
- package/src/ingest/ingest-siblings.ts +487 -0
- package/src/ingest/ingest-status.ts +62 -0
- package/src/ingest/live-ingest-facet.ts +46 -0
- package/src/ingest/module-mode.ts +229 -0
- package/src/ingest/mount-canvas-ingest-root.ts +899 -0
- package/src/ingest/mount-coverage.ts +296 -0
- package/src/ingest/mount-dom-ingest-root.ts +282 -0
- package/src/ingest/mount-ingest-root.ts +744 -0
- package/src/ingest/mount-three-ingest-root.ts +366 -0
- package/src/ingest/resolve-canvas.ts +47 -0
- package/src/ingest/resolve-three.ts +123 -0
- package/src/ingest/served-bundle.ts +109 -0
- package/src/ingest/served-html-boot.ts +292 -0
- package/src/ingest/surface-canvas.ts +84 -0
- package/src/ingest/surface-dom.ts +84 -0
- package/src/ingest/surface-three.ts +128 -0
- package/src/ingest/types.ts +76 -0
- package/src/ingest/unmount-ingest-root.ts +224 -0
- package/src/navmesh/navmesh-actions.ts +27 -0
- package/src/navmesh/navmesh-handler.ts +237 -0
- package/src/navmesh/navmesh-workflow-store.ts +78 -0
- package/src/network/NetworkInspectorPanel.tsx +644 -0
- package/src/network/network-inspector-model.ts +225 -0
- package/src/play/play-boot-stall.ts +118 -0
- package/src/play/play-log-events.ts +26 -0
- package/src/play/play-mode.ts +2553 -0
- package/src/play/play-recording.ts +335 -0
- package/src/play/react-play-live-authoring.ts +165 -0
- package/src/play-bar/PlayBar.tsx +488 -0
- package/src/play-bar/PlayerCountPicker.tsx +100 -0
- package/src/profiler/FrameDebuggerPanel.tsx +458 -0
- package/src/profiler/PerformancePanel.tsx +1068 -0
- package/src/profiler/ProfilerPanel.tsx +53 -0
- package/src/profiler/frame-debugger-store.ts +97 -0
- package/src/profiler/main-thread-busy.ts +90 -0
- package/src/react/design-time-react-mount.ts +782 -0
- package/src/react/dom-authoring-adapter.ts +764 -0
- package/src/react/pasteboard-materialize.ts +154 -0
- package/src/react/react-inspector-section.tsx +2496 -0
- package/src/react/react-world-authoring-adapter.ts +3795 -0
- package/src/react/story-documents/story-args-section.ts +19 -0
- package/src/react/story-documents/story-documents.tsx +1380 -0
- package/src/react/story-paint-bounds.ts +94 -0
- package/src/react/ui-board-document.tsx +101 -0
- package/src/react/ui-board-title.ts +9 -0
- package/src/react/ui-component-board.ts +72 -0
- package/src/services/audio-pose-guard.ts +81 -0
- package/src/services/game-audio-unlock.ts +48 -0
- package/src/state-watch/StateWatchPanel.tsx +535 -0
- package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
- package/src/three/authoring/constraint-inspector-section.tsx +189 -0
- package/src/three/authoring/design-time-renderer.ts +189 -0
- package/src/three/authoring/model-asset-inspector-section.css +41 -0
- package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
- package/src/three/authoring/oid-source-persistence.ts +557 -0
- package/src/three/authoring/r3f-design-session.ts +1174 -0
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
- package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
- package/src/three/authoring/spatial-audio-handles.ts +215 -0
- package/src/three/authoring/spatial-collider-handles.ts +229 -0
- package/src/three/authoring/spatial-joint-handles.ts +114 -0
- package/src/three/authoring/spatial-light-handles.ts +178 -0
- package/src/three/authoring/spatial-lod-handles.ts +93 -0
- package/src/three/authoring/spatial-particle-handles.ts +368 -0
- package/src/three/authoring/three-authoring-adapter.ts +1953 -0
- package/src/three/authoring/three-scene-identity.ts +19 -0
- package/src/three/authoring/three-spatial-handles.ts +161 -0
- package/src/three/authoring/typed-three-inspector.ts +528 -0
- package/src/three/component-verbs/extract-menu.ts +71 -0
- package/src/three/component-verbs/fork-menu.ts +78 -0
- package/src/three/component-verbs/internals-menu.ts +91 -0
- package/src/three/story-documents/three-story-documents.tsx +607 -0
- package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
- package/src/three/three-board/board-framing.ts +412 -0
- package/src/three/three-board/board-layout.ts +401 -0
- package/src/three/three-board/board-scene.ts +901 -0
- package/src/three/three-board/three-component-board.ts +62 -0
- package/src/xstate/XStateBehaviorSection.tsx +130 -0
- package/src/xstate/XStateMachineInspector.tsx +566 -0
- package/src/xstate/character-animation-machine.fixture.ts +74 -0
- package/src/xstate/live-behaviors.ts +106 -0
- package/src/xstate/use-live-actor-state.ts +44 -0
- package/src/xstate/xstate-graph.ts +235 -0
- package/src/xstate/xstate-layout.ts +76 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVERY RELAYED PLAY RECORDS, AND AN IDLE RUN STOPS ITSELF.
|
|
3
|
+
*
|
|
4
|
+
* The defect this closes is not a missing feature — the recorder
|
|
5
|
+
* (`gameplay-recording.ts`) already existed, behind `vgai eval
|
|
6
|
+
* 'editor.recording.start()'`. The defect is that verifying a game through a
|
|
7
|
+
* single screenshot answers a temporal question by luck, and a capability an
|
|
8
|
+
* agent has to remember to switch on is one it does not use. So there is no
|
|
9
|
+
* flag: `vgai play` starts a recording, full stop, and the evidence exists
|
|
10
|
+
* whether or not anyone thought to ask for it.
|
|
11
|
+
*
|
|
12
|
+
* WHY THE RELAYED PLAY AND NOT THE PLAY BUTTON. The binding is in
|
|
13
|
+
* `play.command.ts`'s `'play'` handler, so it covers `vgai play`, the SDK's
|
|
14
|
+
* `play.start`, and nothing else. Clicking Play in the editor is a HUMAN at
|
|
15
|
+
* the surface: they are the witness, the idle rule below ("no session
|
|
16
|
+
* commands and no player input") is meaningless for them, and auto-stopping
|
|
17
|
+
* someone who is watching a cutscene would be a bug, not evidence.
|
|
18
|
+
*
|
|
19
|
+
* WHY AUTO-STOP AT ALL. A WebM is only readable once the recorder finalizes
|
|
20
|
+
* it, so an abandoned play — the agent's turn ended, the harness died, the
|
|
21
|
+
* script threw — leaves a growing file that nobody can open. The idle window
|
|
22
|
+
* is what converts an abandoned run into finished evidence.
|
|
23
|
+
*
|
|
24
|
+
* WHAT RECORDING DOES *NOT* DO: gate the still (2026-08-29). `vgai screenshot`
|
|
25
|
+
* used to REFUSE while a recording was live, on the argument that a single
|
|
26
|
+
* frame answers a temporal question by luck. The argument is right and the
|
|
27
|
+
* refusal was still the defect: because play always records, one `vgai play`
|
|
28
|
+
* killed `vgai screenshot` for the rest of the session — and "what does it
|
|
29
|
+
* look like" is a question a still answers exactly, and the one the whole
|
|
30
|
+
* look/modeling lane asks (measured, cold fox #3: the loop is play → look).
|
|
31
|
+
* Discouraging a wrong question by making a right one impossible is not a
|
|
32
|
+
* trade this product makes. The clip is unchanged and unconditional; the
|
|
33
|
+
* redirect now rides ON the delivered frame
|
|
34
|
+
* (`../bridge/screenshot.ts`'s `screenshotRecordingNotice`), naming the file, its
|
|
35
|
+
* offset-0 wall clock, the play log, and the ten-frame bar. A capture reads
|
|
36
|
+
* the canvas and composites the DOM into an offscreen image, which the
|
|
37
|
+
* recorder's stream never sees, so nothing about the evidence changes.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { isRootCanvas } from '@volter/editor-core/composite-screenshot';
|
|
41
|
+
import { editorConsole } from '@volter/editor-core/editor-console';
|
|
42
|
+
import {
|
|
43
|
+
type GameplayRecordingCapture,
|
|
44
|
+
type GameplayRecordingStarted,
|
|
45
|
+
type GameplayRecordingStartOptions,
|
|
46
|
+
gameplayRecordingActive,
|
|
47
|
+
startGameplayRecording,
|
|
48
|
+
stopGameplayRecording,
|
|
49
|
+
} from '../host/gameplay-recording';
|
|
50
|
+
import { editorHost } from '@volter/editor-sdk/host';
|
|
51
|
+
import type { AudioRecordingHandle } from '@volter/editor-project/adapter';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* How long a play run may go with NO session command and NO player input
|
|
55
|
+
* before it stops itself and finalizes its recording.
|
|
56
|
+
*
|
|
57
|
+
* ONE constant, editor-side, and deliberately generous: the thing it must
|
|
58
|
+
* never do is cut a run that someone is still working with. Two minutes is
|
|
59
|
+
* longer than any gap between an agent's own commands (`vgai eval` round
|
|
60
|
+
* trips are seconds) and longer than a human's pause at the keyboard, while
|
|
61
|
+
* still being short enough that an abandoned run becomes a readable file
|
|
62
|
+
* inside the same working session rather than the next day.
|
|
63
|
+
*/
|
|
64
|
+
export const PLAY_IDLE_AUTOSTOP_MS = 120_000;
|
|
65
|
+
|
|
66
|
+
/** How often the watchdog checks the clock. Coarse on purpose — this is a
|
|
67
|
+
* 2-minute window, and a hidden tab's timers are clamped to about 1 Hz
|
|
68
|
+
* anyway, so anything finer would just be a number that is not true. */
|
|
69
|
+
const IDLE_POLL_MS = 5_000;
|
|
70
|
+
|
|
71
|
+
/** Discrete signals that a human is at the controls. Movement is included
|
|
72
|
+
* because for most games it IS the input; a player aiming a mouse is not
|
|
73
|
+
* idle. Every listener is passive and capture-phase, so nothing here can
|
|
74
|
+
* affect what the game or the editor sees. */
|
|
75
|
+
const INPUT_EVENTS = [
|
|
76
|
+
'keydown',
|
|
77
|
+
'keyup',
|
|
78
|
+
'pointerdown',
|
|
79
|
+
'pointermove',
|
|
80
|
+
'wheel',
|
|
81
|
+
'touchstart',
|
|
82
|
+
'touchmove',
|
|
83
|
+
] as const;
|
|
84
|
+
|
|
85
|
+
interface LivePlayRecording {
|
|
86
|
+
readonly started: GameplayRecordingStarted;
|
|
87
|
+
lastActivityMs: number;
|
|
88
|
+
poll: number | null;
|
|
89
|
+
releaseInput: (() => void) | null;
|
|
90
|
+
/** Set the instant a stop begins, so the watchdog, an explicit stop, and a
|
|
91
|
+
* teardown racing each other cannot finalize the same recording twice. */
|
|
92
|
+
ending: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let live: LivePlayRecording | null = null;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A stop that has BEGUN but not finished.
|
|
99
|
+
*
|
|
100
|
+
* The recorder's own "already active" guard is released at the END of
|
|
101
|
+
* `stopGameplayRecording` (it awaits the in-flight frame, the recorder's stop
|
|
102
|
+
* event, and the chunk uploads). `live` is cleared at the START. So a restart —
|
|
103
|
+
* `vgai play` over a running play, which tears down through `exitPlayMode`'s
|
|
104
|
+
* fire-and-forget teardown and then starts the next recording — would look
|
|
105
|
+
* finished to this module while the recorder was still busy, and the next start
|
|
106
|
+
* would refuse. This handle is what a start waits on; without it the second
|
|
107
|
+
* play of any session silently records nothing.
|
|
108
|
+
*/
|
|
109
|
+
let pendingStop: Promise<GameplayRecordingCapture | null> | null = null;
|
|
110
|
+
/** Which stop owns `pendingStop` — a finished stop must not clear a newer
|
|
111
|
+
* one's handle out from under a start that is waiting on it. */
|
|
112
|
+
let stopGeneration = 0;
|
|
113
|
+
|
|
114
|
+
/** The last recording this session finalized — the "most recent recording"
|
|
115
|
+
* a refusal points at once play is over. */
|
|
116
|
+
let lastFinalized: GameplayRecordingCapture | null = null;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* What ended play, in the caller's own words.
|
|
120
|
+
*
|
|
121
|
+
* `'idle-autostop'` is the one this module produces itself; the rest come from
|
|
122
|
+
* whoever tore play down, so a reader of the journal never has to guess
|
|
123
|
+
* whether a clip ended because the run ended or because nobody was there.
|
|
124
|
+
*/
|
|
125
|
+
export type PlayRecordingEndReason = 'stop' | 'idle-autostop' | 'restart' | 'teardown';
|
|
126
|
+
|
|
127
|
+
/** Called by the auto-stop to end play itself. Bound once, by `play-mode.ts`,
|
|
128
|
+
* so this module never imports the play lifecycle it is driven by. */
|
|
129
|
+
let stopPlay: (() => void) | null = null;
|
|
130
|
+
|
|
131
|
+
export function bindPlayRecordingStop(stop: () => void): void {
|
|
132
|
+
stopPlay = stop;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The editor owns this producer-side wire shape. Its exact keys are pinned by
|
|
136
|
+
* `play-recording.test.ts`; the SDK independently pins its consumer shape. */
|
|
137
|
+
export interface PlayRecordingStatus {
|
|
138
|
+
readonly path: string;
|
|
139
|
+
readonly format: 'composite-webm' | 'canvas-dom';
|
|
140
|
+
readonly replayPath: string | null;
|
|
141
|
+
readonly startedAt: string;
|
|
142
|
+
readonly rotates: boolean;
|
|
143
|
+
readonly logFile: string | null;
|
|
144
|
+
readonly idleAutoStopMs: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The live play recording's facts, or `null` when nothing is recording. */
|
|
148
|
+
export function livePlayRecording(): PlayRecordingStatus | null {
|
|
149
|
+
if (!live || !gameplayRecordingActive()) return null;
|
|
150
|
+
return {
|
|
151
|
+
path: live.started.path,
|
|
152
|
+
format: live.started.format,
|
|
153
|
+
replayPath: live.started.replayPath,
|
|
154
|
+
startedAt: live.started.startedAt,
|
|
155
|
+
rotates: live.started.rotates,
|
|
156
|
+
logFile: live.started.logFile,
|
|
157
|
+
idleAutoStopMs: PLAY_IDLE_AUTOSTOP_MS,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The most recent recording this session finished, or `null`. */
|
|
162
|
+
export function lastPlayRecording(): GameplayRecordingCapture | null {
|
|
163
|
+
return lastFinalized;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Reset the idle clock.
|
|
168
|
+
*
|
|
169
|
+
* Two callers, and the pair is the whole definition of "idle": every relayed
|
|
170
|
+
* session command (`host.session.onCommandDispatched`, subscribed below) and
|
|
171
|
+
* every player input event (the listeners installed below). A run is idle only
|
|
172
|
+
* when BOTH have gone quiet — an agent driving through `vgai eval` and a human
|
|
173
|
+
* holding a movement key are each, alone, enough to keep it alive.
|
|
174
|
+
*/
|
|
175
|
+
export function notePlayActivity(): void {
|
|
176
|
+
if (live) live.lastActivityMs = Date.now();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function installInputListeners(): () => void {
|
|
180
|
+
if (typeof window === 'undefined') return () => undefined;
|
|
181
|
+
const onInput = (): void => notePlayActivity();
|
|
182
|
+
for (const type of INPUT_EVENTS) {
|
|
183
|
+
window.addEventListener(type, onInput, { capture: true, passive: true });
|
|
184
|
+
}
|
|
185
|
+
return () => {
|
|
186
|
+
for (const type of INPUT_EVENTS) {
|
|
187
|
+
window.removeEventListener(type, onInput, { capture: true });
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Start this play run's recording.
|
|
194
|
+
*
|
|
195
|
+
* Never throws: a browser that cannot record must not be a browser that cannot
|
|
196
|
+
* PLAY. The failure is reported to the editor console (which is what
|
|
197
|
+
* `vgai console` and every command's trailing console summary read) and play
|
|
198
|
+
* continues with no clip, rather than an ack that refuses a working game
|
|
199
|
+
* because its evidence path is unavailable.
|
|
200
|
+
*/
|
|
201
|
+
export async function beginPlayRecording(
|
|
202
|
+
container: HTMLElement | null,
|
|
203
|
+
options: {
|
|
204
|
+
name?: string | null;
|
|
205
|
+
audio?: AudioRecordingHandle | null;
|
|
206
|
+
refreshFrame?: () => void;
|
|
207
|
+
canvasFrame?: GameplayRecordingStartOptions['canvasFrame'];
|
|
208
|
+
} = {},
|
|
209
|
+
): Promise<PlayRecordingStatus | null> {
|
|
210
|
+
// A restart plays over a live run. End the old clip first — its bytes are
|
|
211
|
+
// real evidence of the run that just ended, and abandoning the recorder
|
|
212
|
+
// would leave the next start refusing with "already active".
|
|
213
|
+
if (live) await endPlayRecording('restart');
|
|
214
|
+
// …and wait out a stop ALREADY in flight (`exitPlayMode`'s fire-and-forget
|
|
215
|
+
// teardown is exactly that, on every restart). See `pendingStop`.
|
|
216
|
+
await pendingStop;
|
|
217
|
+
if (!container) {
|
|
218
|
+
editorConsole.warn(
|
|
219
|
+
'Play started, but gameplay recording did not: no game surface is mounted to record.',
|
|
220
|
+
'play-recording',
|
|
221
|
+
);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const started = await startGameplayRecording(container, {
|
|
226
|
+
format:
|
|
227
|
+
Array.from(container.querySelectorAll('canvas')).filter(isRootCanvas).length === 1
|
|
228
|
+
? 'canvas-dom'
|
|
229
|
+
: 'composite-webm',
|
|
230
|
+
...(options.name ? { name: options.name } : {}),
|
|
231
|
+
...(options.audio ? { audio: options.audio } : {}),
|
|
232
|
+
...(options.refreshFrame ? { refreshFrame: options.refreshFrame } : {}),
|
|
233
|
+
...(options.canvasFrame ? { canvasFrame: options.canvasFrame } : {}),
|
|
234
|
+
});
|
|
235
|
+
live = {
|
|
236
|
+
started,
|
|
237
|
+
lastActivityMs: Date.now(),
|
|
238
|
+
poll: null,
|
|
239
|
+
releaseInput: installInputListeners(),
|
|
240
|
+
ending: false,
|
|
241
|
+
};
|
|
242
|
+
live.poll = window.setInterval(() => {
|
|
243
|
+
const current = live;
|
|
244
|
+
if (!current || current.ending) return;
|
|
245
|
+
if (Date.now() - current.lastActivityMs < PLAY_IDLE_AUTOSTOP_MS) return;
|
|
246
|
+
void autoStopIdlePlay();
|
|
247
|
+
}, IDLE_POLL_MS);
|
|
248
|
+
return livePlayRecording();
|
|
249
|
+
} catch (error) {
|
|
250
|
+
editorConsole.warn(
|
|
251
|
+
`Play started, but gameplay recording did not: ${error instanceof Error ? error.message : String(error)}`,
|
|
252
|
+
'play-recording',
|
|
253
|
+
);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Finalize this run's recording. Idempotent, and safe to call when nothing is
|
|
260
|
+
* recording (the ordinary case for a play that never got one).
|
|
261
|
+
*/
|
|
262
|
+
export async function endPlayRecording(
|
|
263
|
+
reason: PlayRecordingEndReason,
|
|
264
|
+
): Promise<GameplayRecordingCapture | null> {
|
|
265
|
+
const current = live;
|
|
266
|
+
if (!current || current.ending) return pendingStop;
|
|
267
|
+
current.ending = true;
|
|
268
|
+
if (current.poll !== null) window.clearInterval(current.poll);
|
|
269
|
+
current.releaseInput?.();
|
|
270
|
+
live = null;
|
|
271
|
+
if (!gameplayRecordingActive()) return null;
|
|
272
|
+
// Published BEFORE the first await, so a start racing this stop can find it.
|
|
273
|
+
const generation = ++stopGeneration;
|
|
274
|
+
const stop = (async () => {
|
|
275
|
+
try {
|
|
276
|
+
const capture = await stopGameplayRecording(reason);
|
|
277
|
+
lastFinalized = capture;
|
|
278
|
+
return capture;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
editorConsole.warn(
|
|
281
|
+
`Gameplay recording could not be finalized: ${error instanceof Error ? error.message : String(error)}`,
|
|
282
|
+
'play-recording',
|
|
283
|
+
);
|
|
284
|
+
return null;
|
|
285
|
+
} finally {
|
|
286
|
+
// Only OUR stop clears the slot — a newer one may already own it.
|
|
287
|
+
if (stopGeneration === generation) pendingStop = null;
|
|
288
|
+
}
|
|
289
|
+
})();
|
|
290
|
+
pendingStop = stop;
|
|
291
|
+
return stop;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** One sentence, used by both the auto-stop note and the `vgai screenshot`
|
|
295
|
+
* refusal, so the two doors cannot describe the same clip differently. */
|
|
296
|
+
export function describeCapture(capture: GameplayRecordingCapture): string {
|
|
297
|
+
const seconds = Math.round(capture.durationMs / 100) / 10;
|
|
298
|
+
return (
|
|
299
|
+
`${capture.path} — ${seconds}s at ${capture.effectiveFps} fps` +
|
|
300
|
+
`${capture.hidden ? ' (tab hidden; that is the cadence actually captured, not the requested one)' : ''}`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Nobody has touched this run for the idle window: finalize the evidence and
|
|
306
|
+
* stop play.
|
|
307
|
+
*
|
|
308
|
+
* Recording first, THEN play. A WebM is only readable once its recorder
|
|
309
|
+
* finalizes it, and tearing down the game surface underneath a live composite
|
|
310
|
+
* loop is how a clip ends up truncated at exactly the moment worth watching.
|
|
311
|
+
*
|
|
312
|
+
* This is NOT an error. An unattended run reaching its window is the designed
|
|
313
|
+
* outcome — the point is that the file is closed and named — so it is a
|
|
314
|
+
* console note, and the session journal carries the same fact as a
|
|
315
|
+
* `play-recording` row.
|
|
316
|
+
*/
|
|
317
|
+
async function autoStopIdlePlay(): Promise<void> {
|
|
318
|
+
const idleSeconds = Math.round(PLAY_IDLE_AUTOSTOP_MS / 1000);
|
|
319
|
+
const capture = await endPlayRecording('idle-autostop');
|
|
320
|
+
editorConsole.log(
|
|
321
|
+
capture === null
|
|
322
|
+
? `Play auto-stopped after ${idleSeconds}s with no session command and no player input.`
|
|
323
|
+
: `Play auto-stopped after ${idleSeconds}s with no session command and no player input. ` +
|
|
324
|
+
`Recording finalized: ${describeCapture(capture)}`,
|
|
325
|
+
'play-recording',
|
|
326
|
+
);
|
|
327
|
+
stopPlay?.();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// HALF OF "IS THIS RUN IDLE" — the other half is player input, watched
|
|
331
|
+
// above. Every relayed command counts: an agent that is still driving a game
|
|
332
|
+
// through `vgai eval` is not idle, whatever the command was, and a stamp
|
|
333
|
+
// filed per verb would quietly exclude whichever verb someone forgot.
|
|
334
|
+
// No-op unless a recording is live.
|
|
335
|
+
editorHost().session.onCommandDispatched(() => notePlayActivity());
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `dom` root's PLAY-mode authoring: the SAME {@link ReactRootAuthoringAdapter}
|
|
3
|
+
* Edit mode uses, pointed at an EPHEMERAL destination — the running DOM — instead
|
|
4
|
+
* of the JSX source.
|
|
5
|
+
*
|
|
6
|
+
* Why not the live-only `DomAuthoringAdapter` (the ingest React lane's
|
|
7
|
+
* adapter) here: entity identity. Both this adapter and Edit mode's key nodes by
|
|
8
|
+
* the OID signature `walkOidTree` derives (`o1`, `o1#2`, …); the ingest adapter
|
|
9
|
+
* keys by a STRUCTURAL DOM path (`rdom:div[0]/span[0]`). Swapping adapters at play
|
|
10
|
+
* entry would therefore renumber every dom node, breaking the Edit→Play selection
|
|
11
|
+
* continuity that exists today (
|
|
12
|
+
* asserts the play tree is the OID tree), and would additionally drop the rows
|
|
13
|
+
* only this adapter serves — live component props, design tokens, matched CSS
|
|
14
|
+
* rules, text, empty-container hints. So play keeps the adapter and changes the
|
|
15
|
+
* DESTINATION, which is the only thing play actually needs to change.
|
|
16
|
+
*
|
|
17
|
+
* The destination swap is {@link createEphemeralDomWriteBackend}: a
|
|
18
|
+
* `SourceWriteBackend`-SHAPED writer whose every operation lands on the live DOM
|
|
19
|
+
* and none of which performs any I/O. That shape is the whole seam every write
|
|
20
|
+
* path in `ReactRootAuthoringAdapter` already funnels through (`writeStyleEntry`,
|
|
21
|
+
* `removeStyleProp`, `editText`, `writePropEdit`, `structOp`), so this file adds
|
|
22
|
+
* no second write path and Edit mode is untouched. It deliberately omits
|
|
23
|
+
* `prepare`/`readSource`/`applySource`, which is precisely how
|
|
24
|
+
* `withProjectSourceHistory` (`../history/source-history-backend.ts`) knows to
|
|
25
|
+
* leave it alone: an ephemeral play edit must never enter PROJECT history, and it
|
|
26
|
+
* cannot, because there is no project resource behind it.
|
|
27
|
+
*
|
|
28
|
+
* What that buys, per gesture, matching the three/pixi play regime (edits apply
|
|
29
|
+
* live to the running world, die with the session, never touch source):
|
|
30
|
+
* - style edits / the visibility toggle → the live inline style, at once;
|
|
31
|
+
* - `boxEdit` drags and arrow-key nudges → the live preview `boxEdit.apply`
|
|
32
|
+
* already paints, now COMMITTED live instead of refused at `end`;
|
|
33
|
+
* - `inspector.remove` of a longhand override → the live inline property;
|
|
34
|
+
* - text edits → the live element's text.
|
|
35
|
+
* Two gestures genuinely have no live equivalent and say so at the point of use
|
|
36
|
+
* rather than silently doing nothing: a component PROP is owned by the running
|
|
37
|
+
* React element's fiber (there is no writable live seam — the prop lives at its
|
|
38
|
+
* parent's call site), and a STRUCTURAL op would mutate a tree React's reconciler
|
|
39
|
+
* owns. Both refuse with a message naming the play regime, not the misleading
|
|
40
|
+
* "no dev server" text a backend-less session would print.
|
|
41
|
+
*
|
|
42
|
+
* A live write is honestly transient in both directions: React re-rendering the
|
|
43
|
+
* element repaints over it, exactly as a game's own update loop overwrites a
|
|
44
|
+
* three gizmo drag during play. That is the regime, not a defect.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import type { EditorShellStore } from '@volter/editor-core/editor-shell-store';
|
|
48
|
+
import type { SourceWriteBackend } from '@volter/editor-core/ui-source/source-write-backend';
|
|
49
|
+
import {
|
|
50
|
+
type OidElementLike,
|
|
51
|
+
ReactRootAuthoringAdapter,
|
|
52
|
+
walkOidTree,
|
|
53
|
+
} from '../react/react-world-authoring-adapter';
|
|
54
|
+
import type { AuthoringProvenance } from '@volter/editor-project/adapter';
|
|
55
|
+
|
|
56
|
+
/** Disclosed up front on the adapter, alongside `capabilities.persist: false` and
|
|
57
|
+
* the ephemeral `persistence.destination` play-mode's own wrapper installs — the
|
|
58
|
+
* same `source: 'live'` the adopted three scene reports. */
|
|
59
|
+
export const EPHEMERAL_DOM_PROVENANCE: AuthoringProvenance = {
|
|
60
|
+
source: 'live',
|
|
61
|
+
label: 'live',
|
|
62
|
+
detail:
|
|
63
|
+
'Running DOM adopted from the playing game. Edits apply to this session only ' +
|
|
64
|
+
'and are discarded when play stops.',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const STRUCTURE_REFUSAL =
|
|
68
|
+
'structure is owned by the running React tree — play edits are live-DOM only and are ' +
|
|
69
|
+
'discarded on stop; stop play to change structure in source';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Every live element carrying `oid`. An OID is a SOURCE signature, so one oid can
|
|
73
|
+
* resolve to many rendered elements (a `.map`ped list); a source write would change
|
|
74
|
+
* all of them, so the live write does too.
|
|
75
|
+
*/
|
|
76
|
+
function elementsFor(root: OidElementLike, oid: string): OidElementLike[] {
|
|
77
|
+
const matches: OidElementLike[] = [];
|
|
78
|
+
for (const node of walkOidTree(root).nodes.values()) {
|
|
79
|
+
if (node.oid === oid) matches.push(node.el);
|
|
80
|
+
}
|
|
81
|
+
return matches;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A real `CSSStyleDeclaration` or a plain-object fixture, as a writable bag. */
|
|
85
|
+
function styleOf(el: OidElementLike): Record<string, unknown> | null {
|
|
86
|
+
return el.style ? (el.style as Record<string, unknown>) : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The play regime's write destination — see this file's header. Returns the
|
|
91
|
+
* `SourceWriteBackend` shape so `ReactRootAuthoringAdapter`'s existing write paths
|
|
92
|
+
* carry it unchanged; NOTHING here reads or writes a file, opens a socket, or
|
|
93
|
+
* reaches `/__ui-source`.
|
|
94
|
+
*/
|
|
95
|
+
export function createEphemeralDomWriteBackend(root: OidElementLike): SourceWriteBackend {
|
|
96
|
+
return {
|
|
97
|
+
writeStyle: async (oid, prop, value) => {
|
|
98
|
+
const elements = elementsFor(root, oid);
|
|
99
|
+
if (elements.length === 0) {
|
|
100
|
+
return { changed: false, error: `no live element carries oid "${oid}"` };
|
|
101
|
+
}
|
|
102
|
+
for (const element of elements) {
|
|
103
|
+
const style = styleOf(element);
|
|
104
|
+
if (style) style[prop] = value;
|
|
105
|
+
}
|
|
106
|
+
return { changed: true, route: 'live-dom' };
|
|
107
|
+
},
|
|
108
|
+
removeStyle: async (oid, prop) => {
|
|
109
|
+
const elements = elementsFor(root, oid);
|
|
110
|
+
if (elements.length === 0) {
|
|
111
|
+
return { changed: false, error: `no live element carries oid "${oid}"` };
|
|
112
|
+
}
|
|
113
|
+
// `changed: false` when the property was never set live is the same honest
|
|
114
|
+
// no-op the source writer reports for an unauthored longhand — the caller
|
|
115
|
+
// (`removeStyleProp`) pushes nothing and touches nothing.
|
|
116
|
+
let changed = false;
|
|
117
|
+
for (const element of elements) {
|
|
118
|
+
const style = styleOf(element);
|
|
119
|
+
const current = style?.[prop];
|
|
120
|
+
if (style && current !== undefined && current !== null && current !== '') {
|
|
121
|
+
delete style[prop];
|
|
122
|
+
changed = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { changed, route: 'live-dom' };
|
|
126
|
+
},
|
|
127
|
+
writeText: async (oid, text) => {
|
|
128
|
+
// Leaves only — the same gate `text.get` applies before offering the edit;
|
|
129
|
+
// replacing the text of an element with child elements would delete live
|
|
130
|
+
// children React owns.
|
|
131
|
+
const elements = elementsFor(root, oid).filter((element) => element.children.length === 0);
|
|
132
|
+
if (elements.length === 0) {
|
|
133
|
+
return { changed: false, error: `no live leaf element carries oid "${oid}"` };
|
|
134
|
+
}
|
|
135
|
+
const prevText = elements[0]?.textContent ?? null;
|
|
136
|
+
for (const element of elements) {
|
|
137
|
+
(element as { textContent: string | null }).textContent = text;
|
|
138
|
+
}
|
|
139
|
+
return { changed: true, prevText };
|
|
140
|
+
},
|
|
141
|
+
writeProp: async (_oid, prop) => ({
|
|
142
|
+
changed: false,
|
|
143
|
+
error:
|
|
144
|
+
`prop "${prop}" is owned by the running React element (its value lives at the ` +
|
|
145
|
+
'parent call site, not in the DOM) — play edits are live-DOM only and are discarded ' +
|
|
146
|
+
'on stop; stop play to change it in source',
|
|
147
|
+
}),
|
|
148
|
+
writeStruct: async () => ({ changed: false, error: STRUCTURE_REFUSAL }),
|
|
149
|
+
writeStructMany: async () => ({ changed: false, error: STRUCTURE_REFUSAL }),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The `dom` play child `play-mode.ts` installs: the ordinary React root adapter
|
|
155
|
+
* over the running root, writing live and disclosing that it does.
|
|
156
|
+
*/
|
|
157
|
+
export function createReactPlayAuthoringAdapter(
|
|
158
|
+
root: OidElementLike,
|
|
159
|
+
store: EditorShellStore,
|
|
160
|
+
): ReactRootAuthoringAdapter {
|
|
161
|
+
return new ReactRootAuthoringAdapter(root, store, {
|
|
162
|
+
writeBackend: createEphemeralDomWriteBackend(root),
|
|
163
|
+
provenance: EPHEMERAL_DOM_PROVENANCE,
|
|
164
|
+
});
|
|
165
|
+
}
|