@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.
Files changed (302) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +23 -0
  3. package/contributions/asset-budget-asset.menu.ts +26 -0
  4. package/contributions/asset-budget.action.ts +18 -0
  5. package/contributions/asset-budget.document.tsx +21 -0
  6. package/contributions/asset-budget.menu.ts +22 -0
  7. package/contributions/audio-unlock.service.ts +17 -0
  8. package/contributions/audio.utility.tsx +15 -0
  9. package/contributions/autoplay.service.ts +48 -0
  10. package/contributions/bridge.command.ts +172 -0
  11. package/contributions/build-profiles.document.tsx +21 -0
  12. package/contributions/build-progress.status.tsx +45 -0
  13. package/contributions/build.action.ts +26 -0
  14. package/contributions/build.command.ts +27 -0
  15. package/contributions/build.header.tsx +37 -0
  16. package/contributions/build.menu.ts +31 -0
  17. package/contributions/build.service.ts +31 -0
  18. package/contributions/connection.status.tsx +43 -0
  19. package/contributions/coverage.service.ts +118 -0
  20. package/contributions/edit-mode-audio.service.ts +23 -0
  21. package/contributions/edit-mode-networking.service.ts +30 -0
  22. package/contributions/game-document.service.ts +25 -0
  23. package/contributions/game-eval.command.ts +210 -0
  24. package/contributions/game.layout.ts +19 -0
  25. package/contributions/gameplay.command.ts +255 -0
  26. package/contributions/generation.service.ts +96 -0
  27. package/contributions/generations.status.tsx +53 -0
  28. package/contributions/ingest.service.ts +80 -0
  29. package/contributions/instances.command.ts +75 -0
  30. package/contributions/navmesh.menu.ts +39 -0
  31. package/contributions/navmesh.service.ts +19 -0
  32. package/contributions/network.utility.tsx +17 -0
  33. package/contributions/play.command.ts +355 -0
  34. package/contributions/profiler.action.ts +16 -0
  35. package/contributions/profiler.menu.ts +22 -0
  36. package/contributions/profiler.utility.tsx +17 -0
  37. package/contributions/react/component-board.service.ts +21 -0
  38. package/contributions/react/design-time-mount.service.ts +60 -0
  39. package/contributions/react/pasteboard.action.ts +41 -0
  40. package/contributions/react/react-inspector.service.ts +85 -0
  41. package/contributions/react/story-documents.service.ts +44 -0
  42. package/contributions/scene-document.service.ts +32 -0
  43. package/contributions/state-watch.action.ts +17 -0
  44. package/contributions/state-watch.menu.ts +23 -0
  45. package/contributions/state-watch.utility.tsx +20 -0
  46. package/contributions/team-playtest.service.ts +124 -0
  47. package/contributions/three/camera-runtime.inspector.tsx +34 -0
  48. package/contributions/three/component-board.service.ts +24 -0
  49. package/contributions/three/component-verbs.command.ts +110 -0
  50. package/contributions/three/component-verbs.service.ts +92 -0
  51. package/contributions/three/constraints.inspector.tsx +34 -0
  52. package/contributions/three/model-asset-sections.service.ts +26 -0
  53. package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
  54. package/contributions/three/story-documents.service.ts +31 -0
  55. package/contributions/three/three-authoring.service.ts +66 -0
  56. package/contributions/transport.header.tsx +19 -0
  57. package/contributions/xstate-behavior.action.ts +42 -0
  58. package/contributions/xstate-behavior.document.tsx +73 -0
  59. package/contributions/xstate-behavior.inspector.tsx +28 -0
  60. package/contributions/xstate-behavior.menu.ts +23 -0
  61. package/package.json +144 -0
  62. package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
  63. package/src/asset-budget/asset-budget-model.ts +799 -0
  64. package/src/asset-budget/basis-encoder.ts +165 -0
  65. package/src/asset-budget/gltf-io.ts +154 -0
  66. package/src/asset-budget/gltf-optimize.ts +384 -0
  67. package/src/asset-budget/image-dims.ts +78 -0
  68. package/src/asset-budget/optimize-apply.ts +221 -0
  69. package/src/audio/AudioDebuggerPanel.tsx +457 -0
  70. package/src/audio/audio-debugger-model.ts +87 -0
  71. package/src/bridge/call.ts +60 -0
  72. package/src/bridge/dispatch.ts +459 -0
  73. package/src/bridge/live-frames.ts +25 -0
  74. package/src/bridge/screenshot.ts +316 -0
  75. package/src/build/BuildProfilesPanel.tsx +476 -0
  76. package/src/build/build-session.ts +247 -0
  77. package/src/build/format-bytes.ts +14 -0
  78. package/src/command-results.ts +36 -0
  79. package/src/coverage/live-authoring-surface.ts +30 -0
  80. package/src/coverage/live-project-verbs.ts +162 -0
  81. package/src/coverage/native-system-coverage.ts +143 -0
  82. package/src/coverage/root-coverage.ts +79 -0
  83. package/src/coverage/session-coverage.ts +193 -0
  84. package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
  85. package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
  86. package/src/edit-mode/edit-mode-audio.ts +56 -0
  87. package/src/edit-mode/edit-mode-networking.ts +112 -0
  88. package/src/game-document/DevicePresetPicker.tsx +84 -0
  89. package/src/game-document/GameCaptureFrameButton.tsx +45 -0
  90. package/src/game-document/GameDocument.tsx +215 -0
  91. package/src/game-document/GamePanel.tsx +662 -0
  92. package/src/game-document/InstanceInspectorPicker.tsx +183 -0
  93. package/src/game-document/crowd-debug.ts +183 -0
  94. package/src/game-document/device-preview.ts +336 -0
  95. package/src/game-document/game-view-store.ts +107 -0
  96. package/src/game-document/physics-debug.ts +187 -0
  97. package/src/generation/GenerationActivity.tsx +421 -0
  98. package/src/generation/GenerationGallery.css +231 -0
  99. package/src/generation/generation-documents.tsx +338 -0
  100. package/src/generation/generation-jobs.ts +128 -0
  101. package/src/generation/generation-presentation.ts +257 -0
  102. package/src/host/adapter-reach.ts +445 -0
  103. package/src/host/adapter-runtime-bindings.ts +303 -0
  104. package/src/host/after-paint.ts +112 -0
  105. package/src/host/api/configurations.ts +66 -0
  106. package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
  107. package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
  108. package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
  109. package/src/host/authoring/contract-scenes-stories.ts +204 -0
  110. package/src/host/authoring/creation-site-related.ts +55 -0
  111. package/src/host/authoring/ephemeral-persistence.ts +29 -0
  112. package/src/host/authoring/gesture-persist.ts +84 -0
  113. package/src/host/authoring/ingest-data-writer.ts +232 -0
  114. package/src/host/authoring/ingest-source-persistence.ts +826 -0
  115. package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
  116. package/src/host/authoring/mounted-authoring.ts +41 -0
  117. package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
  118. package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
  119. package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
  120. package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
  121. package/src/host/authoring/pixi-isolation-assets.ts +25 -0
  122. package/src/host/authoring/pixi-live-write-target.ts +979 -0
  123. package/src/host/authoring/pixi-source-identity.ts +141 -0
  124. package/src/host/authoring/pixi-still-presentation.ts +71 -0
  125. package/src/host/authoring/pixi-structure-history.ts +237 -0
  126. package/src/host/authoring/pixi-transform-channels.ts +205 -0
  127. package/src/host/authoring/selection-remount-handoff.ts +23 -0
  128. package/src/host/authoring/source-persistence-backend.ts +373 -0
  129. package/src/host/authoring/source-refresh-revisions.ts +81 -0
  130. package/src/host/authoring/struct-write-pipe.ts +143 -0
  131. package/src/host/auto-frame-window.ts +89 -0
  132. package/src/host/binding-resolver.ts +393 -0
  133. package/src/host/browser-transpile.ts +631 -0
  134. package/src/host/canvas-entry-runtime.ts +95 -0
  135. package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
  136. package/src/host/components/HeaderTelemetry.tsx +341 -0
  137. package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
  138. package/src/host/components/ResolutionPicker.tsx +89 -0
  139. package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
  140. package/src/host/components/frame-debugger-model.ts +579 -0
  141. package/src/host/components/header-telemetry-model.ts +74 -0
  142. package/src/host/components/scene-document.tsx +447 -0
  143. package/src/host/components/utility-view-state.ts +87 -0
  144. package/src/host/components/world-root-stage-binding.tsx +133 -0
  145. package/src/host/components/world-root-stage.ts +1046 -0
  146. package/src/host/coverage/authoring-read-probe.ts +583 -0
  147. package/src/host/coverage/capability-coverage.ts +1587 -0
  148. package/src/host/coverage/coverage-accounting.ts +261 -0
  149. package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
  150. package/src/host/coverage/project-verb-coverage.ts +148 -0
  151. package/src/host/coverage/system-adapter-coverage.ts +373 -0
  152. package/src/host/design-system-stories/StoryLayout.tsx +104 -0
  153. package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
  154. package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
  155. package/src/host/document-preview-three.ts +110 -0
  156. package/src/host/entry-adjudication.ts +89 -0
  157. package/src/host/game-location-guard.ts +150 -0
  158. package/src/host/game-module-access.ts +196 -0
  159. package/src/host/game-realm-page.ts +358 -0
  160. package/src/host/game-realm-reclaim.ts +55 -0
  161. package/src/host/game-realm-storage.ts +104 -0
  162. package/src/host/gameplay-export.ts +288 -0
  163. package/src/host/gameplay-recording.ts +717 -0
  164. package/src/host/gated-globals.ts +1511 -0
  165. package/src/host/history/json-history-resource.ts +254 -0
  166. package/src/host/ingest/registry.ts +261 -0
  167. package/src/host/instance-extract-actions.ts +120 -0
  168. package/src/host/instance-fork-actions.ts +120 -0
  169. package/src/host/play-control-hook.ts +26 -0
  170. package/src/host/playwright-shim.ts +479 -0
  171. package/src/host/projection/dom.ts +295 -0
  172. package/src/host/projection/pixi.ts +288 -0
  173. package/src/host/r3f-entry-runtime.ts +77 -0
  174. package/src/host/react-mount-runtime.ts +162 -0
  175. package/src/host/realm-services.ts +148 -0
  176. package/src/host/recording-preview.ts +106 -0
  177. package/src/host/roots/module-root.ts +203 -0
  178. package/src/host/roots/react-root.ts +181 -0
  179. package/src/host/same-realm-loop-gate.ts +544 -0
  180. package/src/host/scene-view-drawability.ts +69 -0
  181. package/src/host/sdk/tools.ts +31 -0
  182. package/src/host/served-bundle-runtime-modules.ts +331 -0
  183. package/src/host/server-log-bridge.ts +60 -0
  184. package/src/host/staged-projects.ts +24 -0
  185. package/src/host/stories/mounted-story-viewport-source.ts +64 -0
  186. package/src/host/stories/story-arg-descriptors.ts +50 -0
  187. package/src/host/stories/story-media-presence.ts +152 -0
  188. package/src/host/surface-content.ts +87 -0
  189. package/src/host/take-named-export.ts +23 -0
  190. package/src/host/three-ingest-runtime.ts +76 -0
  191. package/src/host/types-fastnoise-lite.d.ts +7 -0
  192. package/src/host/types-mikktspace.d.ts +20 -0
  193. package/src/host/types-troika-three-text.d.ts +7 -0
  194. package/src/host/use-active-performance-source.ts +53 -0
  195. package/src/host/viewport-pose-memory.ts +48 -0
  196. package/src/host/viewport-root-presentation.ts +40 -0
  197. package/src/ingest/active-ingest.ts +251 -0
  198. package/src/ingest/active-scene-navigation.ts +43 -0
  199. package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
  200. package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
  201. package/src/ingest/capture-wait-report.ts +122 -0
  202. package/src/ingest/deferred-ingest-play.ts +216 -0
  203. package/src/ingest/deferred-ingest-session.ts +23 -0
  204. package/src/ingest/discovery-public-ingest.ts +242 -0
  205. package/src/ingest/dom-stub-mark.ts +7 -0
  206. package/src/ingest/entry-load.ts +56 -0
  207. package/src/ingest/game-contract-realm.ts +34 -0
  208. package/src/ingest/game-pointer-lock.ts +89 -0
  209. package/src/ingest/held-scene-repaint.ts +73 -0
  210. package/src/ingest/host-surface-box.ts +174 -0
  211. package/src/ingest/ingest-boot-viewport.ts +54 -0
  212. package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
  213. package/src/ingest/ingest-canvas-scene.ts +50 -0
  214. package/src/ingest/ingest-evidence-hook.ts +131 -0
  215. package/src/ingest/ingest-frame-snapshot.ts +183 -0
  216. package/src/ingest/ingest-play-commands.ts +134 -0
  217. package/src/ingest/ingest-play-control.ts +299 -0
  218. package/src/ingest/ingest-render-debug.ts +320 -0
  219. package/src/ingest/ingest-siblings.ts +487 -0
  220. package/src/ingest/ingest-status.ts +62 -0
  221. package/src/ingest/live-ingest-facet.ts +46 -0
  222. package/src/ingest/module-mode.ts +229 -0
  223. package/src/ingest/mount-canvas-ingest-root.ts +899 -0
  224. package/src/ingest/mount-coverage.ts +296 -0
  225. package/src/ingest/mount-dom-ingest-root.ts +282 -0
  226. package/src/ingest/mount-ingest-root.ts +744 -0
  227. package/src/ingest/mount-three-ingest-root.ts +366 -0
  228. package/src/ingest/resolve-canvas.ts +47 -0
  229. package/src/ingest/resolve-three.ts +123 -0
  230. package/src/ingest/served-bundle.ts +109 -0
  231. package/src/ingest/served-html-boot.ts +292 -0
  232. package/src/ingest/surface-canvas.ts +84 -0
  233. package/src/ingest/surface-dom.ts +84 -0
  234. package/src/ingest/surface-three.ts +128 -0
  235. package/src/ingest/types.ts +76 -0
  236. package/src/ingest/unmount-ingest-root.ts +224 -0
  237. package/src/navmesh/navmesh-actions.ts +27 -0
  238. package/src/navmesh/navmesh-handler.ts +237 -0
  239. package/src/navmesh/navmesh-workflow-store.ts +78 -0
  240. package/src/network/NetworkInspectorPanel.tsx +644 -0
  241. package/src/network/network-inspector-model.ts +225 -0
  242. package/src/play/play-boot-stall.ts +118 -0
  243. package/src/play/play-log-events.ts +26 -0
  244. package/src/play/play-mode.ts +2553 -0
  245. package/src/play/play-recording.ts +335 -0
  246. package/src/play/react-play-live-authoring.ts +165 -0
  247. package/src/play-bar/PlayBar.tsx +488 -0
  248. package/src/play-bar/PlayerCountPicker.tsx +100 -0
  249. package/src/profiler/FrameDebuggerPanel.tsx +458 -0
  250. package/src/profiler/PerformancePanel.tsx +1068 -0
  251. package/src/profiler/ProfilerPanel.tsx +53 -0
  252. package/src/profiler/frame-debugger-store.ts +97 -0
  253. package/src/profiler/main-thread-busy.ts +90 -0
  254. package/src/react/design-time-react-mount.ts +782 -0
  255. package/src/react/dom-authoring-adapter.ts +764 -0
  256. package/src/react/pasteboard-materialize.ts +154 -0
  257. package/src/react/react-inspector-section.tsx +2496 -0
  258. package/src/react/react-world-authoring-adapter.ts +3795 -0
  259. package/src/react/story-documents/story-args-section.ts +19 -0
  260. package/src/react/story-documents/story-documents.tsx +1380 -0
  261. package/src/react/story-paint-bounds.ts +94 -0
  262. package/src/react/ui-board-document.tsx +101 -0
  263. package/src/react/ui-board-title.ts +9 -0
  264. package/src/react/ui-component-board.ts +72 -0
  265. package/src/services/audio-pose-guard.ts +81 -0
  266. package/src/services/game-audio-unlock.ts +48 -0
  267. package/src/state-watch/StateWatchPanel.tsx +535 -0
  268. package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
  269. package/src/three/authoring/constraint-inspector-section.tsx +189 -0
  270. package/src/three/authoring/design-time-renderer.ts +189 -0
  271. package/src/three/authoring/model-asset-inspector-section.css +41 -0
  272. package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
  273. package/src/three/authoring/oid-source-persistence.ts +557 -0
  274. package/src/three/authoring/r3f-design-session.ts +1174 -0
  275. package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
  276. package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
  277. package/src/three/authoring/spatial-audio-handles.ts +215 -0
  278. package/src/three/authoring/spatial-collider-handles.ts +229 -0
  279. package/src/three/authoring/spatial-joint-handles.ts +114 -0
  280. package/src/three/authoring/spatial-light-handles.ts +178 -0
  281. package/src/three/authoring/spatial-lod-handles.ts +93 -0
  282. package/src/three/authoring/spatial-particle-handles.ts +368 -0
  283. package/src/three/authoring/three-authoring-adapter.ts +1953 -0
  284. package/src/three/authoring/three-scene-identity.ts +19 -0
  285. package/src/three/authoring/three-spatial-handles.ts +161 -0
  286. package/src/three/authoring/typed-three-inspector.ts +528 -0
  287. package/src/three/component-verbs/extract-menu.ts +71 -0
  288. package/src/three/component-verbs/fork-menu.ts +78 -0
  289. package/src/three/component-verbs/internals-menu.ts +91 -0
  290. package/src/three/story-documents/three-story-documents.tsx +607 -0
  291. package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
  292. package/src/three/three-board/board-framing.ts +412 -0
  293. package/src/three/three-board/board-layout.ts +401 -0
  294. package/src/three/three-board/board-scene.ts +901 -0
  295. package/src/three/three-board/three-component-board.ts +62 -0
  296. package/src/xstate/XStateBehaviorSection.tsx +130 -0
  297. package/src/xstate/XStateMachineInspector.tsx +566 -0
  298. package/src/xstate/character-animation-machine.fixture.ts +74 -0
  299. package/src/xstate/live-behaviors.ts +106 -0
  300. package/src/xstate/use-live-actor-state.ts +44 -0
  301. package/src/xstate/xstate-graph.ts +235 -0
  302. package/src/xstate/xstate-layout.ts +76 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * "Fork Component…".
3
+ *
4
+ * The applicability rule and every sentence this action can say, kept pure and
5
+ * adapter-free for the same reason `instance-source-actions.ts` (H3) is — the
6
+ * wiring that knows about adapters lives in
7
+ * `authoring/instance-fork-menu.ts`, and the SOURCE REWRITE lives server-side in
8
+ * `ui-source/plan-fork-component.ts`.
9
+ *
10
+ * THE LABEL IS "Fork Component…", NOT "fork for this instance". H7's own title
11
+ * says "for this instance", and it is worth being precise about what the edit
12
+ * actually reaches: it retargets ONE CALLSITE. When that callsite sits inside a
13
+ * component that is itself rendered many times, every one of those renders now
14
+ * renders the fork. One callsite is the unit of the edit; one callsite is not
15
+ * always one object on screen.
16
+ *
17
+ * The trailing ellipsis follows the platform convention the panel already
18
+ * implies for an action with consequences beyond the row — a fork writes a NEW
19
+ * FILE, which no other hierarchy item does.
20
+ */
21
+
22
+ import type { InstanceSourceLocator } from '@volter/editor-core/instance-source-actions';
23
+
24
+ export const FORK_COMPONENT_LABEL = 'Fork Component…';
25
+
26
+ /**
27
+ * The adapter surface this action reads. `canForkComponent` is the adapter's
28
+ * own honest answer to "could I write this?" (a write backend exists, this id
29
+ * has both a callsite and a definition); `forkComponent` performs it and
30
+ * returns the sentence to show.
31
+ */
32
+ export interface InstanceForkSource extends InstanceSourceLocator {
33
+ readonly canForkComponent?: ((id: string) => boolean) | undefined;
34
+ readonly forkComponent?: ((id: string) => Promise<string>) | undefined;
35
+ }
36
+
37
+ /**
38
+ * H7 applicability. Three conditions, all of them about whether the edit is
39
+ * REACHABLE — never about whether it would succeed, which only the plan can
40
+ * say:
41
+ *
42
+ * 1. the row is a component instance (`role: 'component'`, H1's own predicate);
43
+ * 2. BOTH source destinations resolve — the callsite is the tag this action
44
+ * retargets and the definition is the module it copies, so a row missing
45
+ * either has nothing to fork;
46
+ * 3. the adapter can actually write (a dev-server write backend is present).
47
+ *
48
+ * Like H3, an inapplicable row gets NO item rather than a disabled one: the
49
+ * registry has no disabled state, and the panel's idiom is to omit an item
50
+ * whose subject is absent. Everything the PLAN rejects is a click-time
51
+ * transient hint carrying the plan's own named reason.
52
+ */
53
+ export function canForkInstance(
54
+ node: { readonly role?: string | undefined } | null | undefined,
55
+ source: InstanceForkSource | null | undefined,
56
+ id: string,
57
+ ): boolean {
58
+ if (node?.role !== 'component') return false;
59
+ if (!source || typeof source.forkComponent !== 'function') return false;
60
+ if (typeof source.sourceLocation !== 'function' || !source.sourceLocation(id)) return false;
61
+ if (typeof source.definitionLocation !== 'function' || !source.definitionLocation(id)) {
62
+ return false;
63
+ }
64
+ return source.canForkComponent?.(id) !== false;
65
+ }
66
+
67
+ // ------------------------------------------------------------------ sentences
68
+
69
+ /**
70
+ * The success hint. It NAMES THE NEW FILE, which is the whole reason this is a
71
+ * hint and not silence: the author has to know what was created, because undo
72
+ * (which owns the callsite edit) will not remove it.
73
+ */
74
+ export function forkedHint(tag: string, newName: string, newPath: string): string {
75
+ return (
76
+ `Forked <${tag}> → <${newName}> (${newPath}). ` +
77
+ 'Undo restores the callsite; the new file stays.'
78
+ );
79
+ }
80
+
81
+ /** The row lost its callsite or definition between menu and click. */
82
+ export function forkUnavailableHint(tag: string): string {
83
+ return `<${tag}> is no longer in the source index — there is nothing to fork.`;
84
+ }
85
+
86
+ /** No dev-server write backend in this session (a hosted or read-only tier). */
87
+ export function forkNoBackendHint(tag: string): string {
88
+ return `This session cannot write project source, so <${tag}> cannot be forked.`;
89
+ }
90
+
91
+ /**
92
+ * The new file was written and the CALLSITE edit then failed. Reported in full
93
+ * rather than swallowed: the copy is inert (nothing imports it), so the failure
94
+ * mode is clutter, not breakage — and the author is told exactly that, plus the
95
+ * path, so they can delete it or retry.
96
+ */
97
+ export function forkPartialHint(
98
+ tag: string,
99
+ newName: string,
100
+ newPath: string,
101
+ detail: string,
102
+ ): string {
103
+ return (
104
+ `Wrote ${newPath} for <${newName}>, but the <${tag}> callsite was not retargeted (${detail}). ` +
105
+ 'Nothing imports the new file yet, so it is inert — delete it or try again.'
106
+ );
107
+ }
108
+
109
+ /** Any other refusal: the plan's own named reason, or a transport failure. */
110
+ export function forkRefusedHint(reason: string): string {
111
+ return `Cannot fork: ${reason}`;
112
+ }
113
+
114
+ /** Did a hint sentence report a LANDED fork? Owned by this module — the one
115
+ * place the sentences are minted — so the command door's ok/error split
116
+ * cannot drift from the wording it matches. The extract twin is
117
+ * `isExtractedHint`. */
118
+ export function isForkedHint(hint: string): boolean {
119
+ return hint.startsWith('Forked ');
120
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The ONE stable hook on the editor's play control.
3
+ *
4
+ * `PlayBar` renders two different transports — the native one (title "Play
5
+ * (run game)") and the ingest one (label "Run ingested game") — and anything
6
+ * outside the component that needs to find "the play control" was matching a
7
+ * LABEL. `vgai doctor` polled for the native string as its editor-readiness
8
+ * boundary and therefore waited out its whole timeout budget on every ingest
9
+ * project, which renders the other one (SimCity ingest dogfood, S-6).
10
+ *
11
+ * A label is a product decision that moves; this hook is not. Both transports
12
+ * carry it, so a caller asking "is the play control up?" gets one selector
13
+ * that is true on every surface — and adding a THIRD transport means adding
14
+ * the hook, not teaching every caller a third string.
15
+ *
16
+ * Deliberately a dependency-free leaf module: `packages/editor/scripts/
17
+ * run-doctor.ts` runs under plain `tsx` (no Vite, no DOM, and it imports by
18
+ * RELATIVE path because tsx resolves the package/`@editor/*` aliases from
19
+ * the process CWD — only a repo-root cwd has them) and imports this directly,
20
+ * so it must never pull React or the design system in.
21
+ */
22
+
23
+ export const PLAY_CONTROL_TEST_ID = 'play-control';
24
+
25
+ /** CSS selector form — what Playwright/`querySelector` callers want. */
26
+ export const PLAY_CONTROL_SELECTOR = `[data-testid="${PLAY_CONTROL_TEST_ID}"]`;
@@ -0,0 +1,479 @@
1
+ /**
2
+ * "one dialect, full capability" (second bullet) — an in-page shim
3
+ * implementing a SUBSET of Playwright's own `Page`/`Locator` surface, rooted at
4
+ * an arbitrary `HTMLElement` (the play-mode game container, `play-mode.ts`'s
5
+ * `getGameContainer()` — see interface doctrine §3.2: "The AI should think it
6
+ * is basically just executing Playwright"). It runs INSIDE the already-open
7
+ * editor tab (no browser automation, no CDP) — `command-listener.ts`'s
8
+ * `page-script` relay op constructs one per call and hands it to a
9
+ * reconstructed step function; a real `@playwright/test` `Page` drives the SAME
10
+ * step file when the spec runs under `PageTransport`
11
+ * (`packages/vgai-live/src/game-client/client.ts`) in CI. That duality is why
12
+ * `createPageShim`'s return value is typed `as Page` — every implemented member
13
+ * matches Playwright's own shape closely enough that a step written against it
14
+ * compiles against the real type too.
15
+ *
16
+ * ## Honesty boundary (spec §3.2, §4 rung 4, §4's honesty-boundary bullet)
17
+ *
18
+ * This shim is for DOM interaction — menus, dialogs, HUD controls, and
19
+ * React-only games whose gameplay surface is the DOM itself. Its
20
+ * events are synthesized DOM events (`dispatchEvent`, always `isTrusted:
21
+ * false` — inherent to the DOM API, never faked or hidden) with no real hit
22
+ * testing. Canvas/adapter gameplay input must still use the engine's virtual-
23
+ * action seam (`game.input.*`); a React-only game uses `game.page(...)` because
24
+ * DOM keyboard/click handlers are its real player-facing input path.
25
+ *
26
+ * Every Playwright `Page`/`Locator` member NOT implemented below throws
27
+ * `PageShimUnsupportedError` naming itself and listing what IS supported —
28
+ * per the interface doctrine, "the illusion may be incomplete, never
29
+ * silently wrong": a step calling `page.mouse.move(...)` must fail loudly the
30
+ * instant it touches the unsupported member, not silently no-op.
31
+ *
32
+ * ## The wire's closure-capture limitation (decision 2)
33
+ *
34
+ * A `page-script` step travels the wire as `step.toString()` and is
35
+ * reconstructed here with `new Function('page', 'return (' + src + ')(page)')`
36
+ * — the SAME limitation class as Playwright's own `page.evaluate`
37
+ * serialization: closures over anything outside the step's own function body
38
+ * (outer `const`s, imported helpers, captured test state) do NOT survive the
39
+ * trip. Only `page` itself (this shim's return value) and whatever the step
40
+ * computes/inlines internally are available. Specs meant to run unmodified
41
+ * under both `PageTransport` (real Playwright, real closures) and
42
+ * `RelayTransport` (this shim, serialized) must be written as though ALWAYS
43
+ * serialized — inline every value the step needs; see
44
+ * `packages/vgai-live/src/game-client/client.ts`'s `GameClient.page()` doc comment for
45
+ * the client-facing half of this contract.
46
+ */
47
+
48
+ import type { Locator, Page } from '@playwright/test';
49
+
50
+ /** Thrown by every unimplemented `Page`/`Locator` member this shim proxies.
51
+ * `member` is the exact property name touched; `supported` is the sibling
52
+ * member list at the SAME object level (Page's own supported members, or a
53
+ * Locator's own), so the thrown message alone is enough for an agent to
54
+ * self-correct without reading this file. */
55
+ export class PageShimUnsupportedError extends Error {
56
+ readonly member: string;
57
+ readonly supported: readonly string[];
58
+
59
+ constructor(objectName: string, member: string, supported: readonly string[]) {
60
+ super(
61
+ `playwright-shim: ${objectName}.${member} is not supported by the in-page shim. ` +
62
+ `Supported ${objectName} members: ${supported.join(', ')}. ` +
63
+ 'This shim is DOM-only — canvas gameplay input must use game.input.*, while React-only ' +
64
+ 'DOM games may use Page keyboard/locator input. See packages/editor/src/playwright-shim.ts for ' +
65
+ "the full supported subset and the wire's closure-capture limitation.",
66
+ );
67
+ this.name = 'PageShimUnsupportedError';
68
+ this.member = member;
69
+ this.supported = supported;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Wraps a plain object so that reading any property NOT already present
75
+ * (own OR inherited — `in` walks the prototype chain, so ordinary
76
+ * `Object.prototype` members like `toString`/`hasOwnProperty` pass through
77
+ * harmlessly) throws `PageShimUnsupportedError` instead of returning
78
+ * `undefined`. Symbol-keyed access (`Symbol.toPrimitive`, Node's
79
+ * `util.inspect.custom`, etc. — console/debugger machinery that probes
80
+ * objects defensively) is let through as `undefined` rather than throwing,
81
+ * so merely logging or inspecting a shim object never itself explodes.
82
+ */
83
+ function wrapWithUnsupportedProxy<T extends object>(target: T, objectName: string): T {
84
+ const supported = Object.keys(target);
85
+ return new Proxy(target, {
86
+ get(obj, prop, receiver) {
87
+ if (typeof prop === 'symbol') return Reflect.get(obj, prop, receiver);
88
+ if (prop in obj) return Reflect.get(obj, prop, receiver);
89
+ throw new PageShimUnsupportedError(objectName, prop, supported);
90
+ },
91
+ }) as T;
92
+ }
93
+
94
+ // --- accessible-name / role approximation (a reasonable subset, per brief) -
95
+
96
+ function normalizeWs(s: string): string {
97
+ return s.replace(/\s+/g, ' ').trim();
98
+ }
99
+
100
+ function impliedRole(el: Element): string | null {
101
+ const tag = el.tagName.toLowerCase();
102
+ if (tag === 'button') return 'button';
103
+ if (tag === 'a' && el.hasAttribute('href')) return 'link';
104
+ if (/^h[1-6]$/.test(tag)) return 'heading';
105
+ if (tag === 'textarea') return 'textbox';
106
+ if (tag === 'select') return 'combobox';
107
+ if (tag === 'img') return 'img';
108
+ if (tag === 'input') {
109
+ const type = (el.getAttribute('type') ?? 'text').toLowerCase();
110
+ if (type === 'button' || type === 'submit' || type === 'reset') return 'button';
111
+ if (type === 'checkbox') return 'checkbox';
112
+ if (type === 'radio') return 'radio';
113
+ return 'textbox';
114
+ }
115
+ return null;
116
+ }
117
+
118
+ function roleOf(el: Element): string | null {
119
+ return el.getAttribute('role') ?? impliedRole(el);
120
+ }
121
+
122
+ /** Accessible-name approximation, exactly per brief: `aria-label` first, then
123
+ * trimmed `textContent`. Real accessible-name computation (aria-labelledby,
124
+ * associated `<label>`, placeholder fallback, etc.) is out of scope for this
125
+ * "reasonable subset". */
126
+ function accessibleName(el: Element): string {
127
+ const ariaLabel = el.getAttribute('aria-label');
128
+ if (ariaLabel && ariaLabel.trim()) return normalizeWs(ariaLabel);
129
+ return normalizeWs(el.textContent ?? '');
130
+ }
131
+
132
+ function matchesName(el: Element, name: string | RegExp | undefined): boolean {
133
+ if (name === undefined) return true;
134
+ const accName = accessibleName(el);
135
+ return name instanceof RegExp ? name.test(accName) : accName === name;
136
+ }
137
+
138
+ function queryByRole(
139
+ root: HTMLElement,
140
+ role: string,
141
+ opts?: { name?: string | RegExp },
142
+ ): Element[] {
143
+ const all = Array.from(root.querySelectorAll('*'));
144
+ return all.filter((el) => roleOf(el) === role && matchesName(el, opts?.name));
145
+ }
146
+
147
+ /** Substring match on normalized whitespace text (Playwright's own
148
+ * `getByText` default), keeping only the most SPECIFIC matches — an
149
+ * ancestor whose own match is purely inherited from an already-matching
150
+ * descendant is dropped, approximating Playwright's leaf-text bias. */
151
+ function queryByText(root: HTMLElement, text: string | RegExp): Element[] {
152
+ const all = Array.from(root.querySelectorAll('*')).filter(
153
+ (el) => el.tagName !== 'SCRIPT' && el.tagName !== 'STYLE',
154
+ );
155
+ const isMatch = (el: Element): boolean => {
156
+ const t = normalizeWs(el.textContent ?? '');
157
+ if (t.length === 0) return false;
158
+ return text instanceof RegExp ? text.test(t) : t.includes(text);
159
+ };
160
+ const matched = all.filter(isMatch);
161
+ return matched.filter((el) => !matched.some((other) => other !== el && el.contains(other)));
162
+ }
163
+
164
+ function queryByTestId(root: HTMLElement, testId: string | RegExp): Element[] {
165
+ return Array.from(root.querySelectorAll('[data-testid]')).filter((el) => {
166
+ const value = el.getAttribute('data-testid') ?? '';
167
+ return testId instanceof RegExp ? testId.test(value) : value === testId;
168
+ });
169
+ }
170
+
171
+ function isVisible(el: Element): boolean {
172
+ if (!(el instanceof HTMLElement)) return true;
173
+ if (el.hidden) return false;
174
+ const style = getComputedStyle(el);
175
+ return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
176
+ }
177
+
178
+ // --- event dispatch (isTrusted:false by construction — dispatchEvent's own
179
+ // contract, never spoofed) -------------------------------------------------
180
+
181
+ function dispatchClick(el: Element): void {
182
+ const rect = (el as HTMLElement).getBoundingClientRect?.() ?? {
183
+ left: 0,
184
+ top: 0,
185
+ width: 0,
186
+ height: 0,
187
+ };
188
+ const clientX = rect.left + rect.width / 2;
189
+ const clientY = rect.top + rect.height / 2;
190
+ // `view` is deliberately omitted: Playwright's own synthesized events make
191
+ // no promise about it either, and supplying one risks a cross-realm
192
+ // mismatch (the element's owner window vs. whatever `Window` class the
193
+ // event constructors currently in scope expect) — omitting it sidesteps
194
+ // that entirely with no loss of fidelity for a UI-only automation shim.
195
+ const mouseInit: MouseEventInit = {
196
+ bubbles: true,
197
+ cancelable: true,
198
+ clientX,
199
+ clientY,
200
+ button: 0,
201
+ };
202
+ if (typeof PointerEvent !== 'undefined') {
203
+ el.dispatchEvent(
204
+ new PointerEvent('pointerdown', { ...mouseInit, pointerId: 1, isPrimary: true }),
205
+ );
206
+ }
207
+ el.dispatchEvent(new MouseEvent('mousedown', mouseInit));
208
+ if (typeof PointerEvent !== 'undefined') {
209
+ el.dispatchEvent(
210
+ new PointerEvent('pointerup', { ...mouseInit, pointerId: 1, isPrimary: true }),
211
+ );
212
+ }
213
+ el.dispatchEvent(new MouseEvent('mouseup', mouseInit));
214
+ el.dispatchEvent(new MouseEvent('click', mouseInit));
215
+ }
216
+
217
+ /** React-compatible value set: React's synthetic `onChange` listens through
218
+ * a wrapped native setter, so assigning `el.value = ...` directly is a
219
+ * no-op from React's perspective — bypass it via the PROTOTYPE's own
220
+ * setter (the well-known trick), then fire real `input`/`change` events. */
221
+ function nativeValueSetter(el: HTMLInputElement | HTMLTextAreaElement): (value: string) => void {
222
+ const proto =
223
+ el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
224
+ const descriptor = Object.getOwnPropertyDescriptor(proto, 'value');
225
+ return (value: string) => {
226
+ if (descriptor?.set) {
227
+ descriptor.set.call(el, value);
228
+ } else {
229
+ el.value = value;
230
+ }
231
+ };
232
+ }
233
+
234
+ function dispatchFill(el: Element, value: string): void {
235
+ if (!(el instanceof HTMLInputElement) && !(el instanceof HTMLTextAreaElement)) {
236
+ throw new Error(
237
+ `playwright-shim: fill() target is not an <input>/<textarea> (got <${el.tagName.toLowerCase()}>)`,
238
+ );
239
+ }
240
+ el.focus();
241
+ nativeValueSetter(el)(value);
242
+ el.dispatchEvent(new Event('input', { bubbles: true }));
243
+ el.dispatchEvent(new Event('change', { bubbles: true }));
244
+ }
245
+
246
+ // --- keyboard --------------------------------------------------------------
247
+
248
+ const MODIFIER_KEYS = new Set(['Alt', 'Control', 'Meta', 'Shift']);
249
+
250
+ function normalizedKey(key: string): string {
251
+ if (key !== 'ControlOrMeta') return key;
252
+ return /Mac|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Control';
253
+ }
254
+
255
+ function codeForKey(key: string): string {
256
+ if (/^[a-z]$/i.test(key)) return `Key${key.toUpperCase()}`;
257
+ if (/^[0-9]$/.test(key)) return `Digit${key}`;
258
+ if (key === ' ') return 'Space';
259
+ return key;
260
+ }
261
+
262
+ function keyboardTarget(root: HTMLElement): HTMLElement {
263
+ const active = document.activeElement;
264
+ return active instanceof HTMLElement && root.contains(active) ? active : root;
265
+ }
266
+
267
+ function dispatchKey(
268
+ root: HTMLElement,
269
+ type: 'keydown' | 'keyup',
270
+ key: string,
271
+ pressed: ReadonlySet<string>,
272
+ ): void {
273
+ keyboardTarget(root).dispatchEvent(
274
+ new KeyboardEvent(type, {
275
+ key,
276
+ code: codeForKey(key),
277
+ bubbles: true,
278
+ cancelable: true,
279
+ altKey: pressed.has('Alt'),
280
+ ctrlKey: pressed.has('Control'),
281
+ metaKey: pressed.has('Meta'),
282
+ shiftKey: pressed.has('Shift'),
283
+ }),
284
+ );
285
+ }
286
+
287
+ function makeKeyboard(root: HTMLElement) {
288
+ const pressed = new Set<string>();
289
+ const down = async (rawKey: string): Promise<void> => {
290
+ const key = normalizedKey(rawKey);
291
+ pressed.add(key);
292
+ dispatchKey(root, 'keydown', key, pressed);
293
+ };
294
+ const up = async (rawKey: string): Promise<void> => {
295
+ const key = normalizedKey(rawKey);
296
+ dispatchKey(root, 'keyup', key, pressed);
297
+ pressed.delete(key);
298
+ };
299
+ return {
300
+ down,
301
+ up,
302
+ async press(shortcut: string): Promise<void> {
303
+ const keys = shortcut.split('+').map(normalizedKey);
304
+ const modifiers = keys.filter((key) => MODIFIER_KEYS.has(key));
305
+ const primary = keys.find((key) => !MODIFIER_KEYS.has(key));
306
+ for (const modifier of modifiers) await down(modifier);
307
+ if (primary) {
308
+ await down(primary);
309
+ await up(primary);
310
+ }
311
+ for (const modifier of modifiers.reverse()) await up(modifier);
312
+ },
313
+ };
314
+ }
315
+
316
+ // --- ariaSnapshot ------------------------------------------------------
317
+
318
+ /** A simple indented role/name tree — "enough for an agent to orient" (per
319
+ * brief), not a byte-accurate accessibility-tree dump. Only elements with a
320
+ * resolvable role appear; their descendants nest one level deeper. */
321
+ function buildAriaSnapshot(root: Element): string {
322
+ const lines: string[] = [];
323
+ function walk(el: Element, depth: number): void {
324
+ const role = roleOf(el);
325
+ if (role) {
326
+ const name = accessibleName(el);
327
+ lines.push(`${' '.repeat(depth)}- ${role}${name ? ` "${name}"` : ''}`);
328
+ }
329
+ const nextDepth = role ? depth + 1 : depth;
330
+ for (const child of Array.from(el.children)) walk(child, nextDepth);
331
+ }
332
+ for (const child of Array.from(root.children)) walk(child, 0);
333
+ return lines.join('\n');
334
+ }
335
+
336
+ // --- Locator -------------------------------------------------------------
337
+
338
+ function makeLocator(query: () => Element[], description: string): Locator {
339
+ const resolveFirst = (action: string): Element => {
340
+ const el = query()[0];
341
+ if (!el) {
342
+ throw new Error(`playwright-shim: ${action}() found no element for ${description}`);
343
+ }
344
+ return el;
345
+ };
346
+
347
+ const locatorObj = {
348
+ async click(): Promise<void> {
349
+ dispatchClick(resolveFirst('click'));
350
+ },
351
+ async fill(value: string): Promise<void> {
352
+ dispatchFill(resolveFirst('fill'), value);
353
+ },
354
+ async textContent(): Promise<string | null> {
355
+ const el = query()[0];
356
+ return el ? (el.textContent ?? null) : null;
357
+ },
358
+ async evaluate<R, Arg>(
359
+ pageFunction: (element: Element, arg: Arg) => R | Promise<R>,
360
+ arg?: Arg,
361
+ ): Promise<R> {
362
+ return pageFunction(resolveFirst('evaluate'), arg as Arg);
363
+ },
364
+ async count(): Promise<number> {
365
+ return query().length;
366
+ },
367
+ async waitFor(opts?: { state?: 'visible' | 'attached'; timeout?: number }): Promise<void> {
368
+ const timeoutMs = opts?.timeout ?? 5000;
369
+ const wantState = opts?.state ?? 'visible';
370
+ const deadline = Date.now() + timeoutMs;
371
+ for (;;) {
372
+ const el = query()[0];
373
+ if (el && (wantState === 'attached' || isVisible(el))) return;
374
+ if (Date.now() >= deadline) {
375
+ throw new Error(
376
+ `playwright-shim: waitFor() timed out after ${timeoutMs}ms waiting for ${description} ` +
377
+ `to be ${wantState}`,
378
+ );
379
+ }
380
+ await new Promise((resolve) => setTimeout(resolve, 50));
381
+ }
382
+ },
383
+ first(): Locator {
384
+ return makeLocator(() => query().slice(0, 1), `${description}.first()`);
385
+ },
386
+ nth(index: number): Locator {
387
+ return makeLocator(() => {
388
+ const el = query()[index];
389
+ return el ? [el] : [];
390
+ }, `${description}.nth(${index})`);
391
+ },
392
+ // Real Playwright places `ariaSnapshot` on `Locator` (not `Page`) — kept
393
+ // here for parity with the real type; `createPageShim`'s own
394
+ // convenience `ariaSnapshot()` (see `PageShim` below) is a root-rooted
395
+ // shorthand for `page.locator(':scope').ariaSnapshot()`.
396
+ async ariaSnapshot(): Promise<string> {
397
+ return buildAriaSnapshot(resolveFirst('ariaSnapshot'));
398
+ },
399
+ };
400
+ return wrapWithUnsupportedProxy(locatorObj, 'Locator') as unknown as Locator;
401
+ }
402
+
403
+ // --- evaluate --------------------------------------------------------------
404
+
405
+ /** Accepts either a real function (called directly — no serialization
406
+ * needed since the shim already runs in-page, unlike real Playwright's
407
+ * cross-process `evaluate`) or a function-source string (reconstructed via
408
+ * `new Function`, same wire contract as the outer `page-script` step
409
+ * itself — see this module's own doc comment). */
410
+ async function evaluateImpl(fn: unknown, arg?: unknown): Promise<unknown> {
411
+ if (typeof fn === 'function') {
412
+ return (fn as (a: unknown) => unknown)(arg);
413
+ }
414
+ if (typeof fn === 'string') {
415
+ // Reconstructing a wire-carried function's source is this shim's
416
+ // documented contract (decision 2) — the same limitation class as
417
+ // Playwright's own evaluate serialization.
418
+ const reconstructed = new Function('arg', `return (${fn})(arg)`) as (a: unknown) => unknown;
419
+ return reconstructed(arg);
420
+ }
421
+ throw new Error('playwright-shim: evaluate() requires a function or a function-source string');
422
+ }
423
+
424
+ // --- Page ------------------------------------------------------------------
425
+
426
+ /**
427
+ * `createPageShim`'s return type: real Playwright's `Page` plus ONE
428
+ * shim-specific convenience, `ariaSnapshot()` — real Playwright only places
429
+ * `ariaSnapshot` on `Locator` (`page.locator(...).ariaSnapshot()`), which
430
+ * this shim ALSO implements (see `makeLocator`, above) for byte-parity with
431
+ * real specs. The root-level shorthand here exists because "enough for an
432
+ * agent to orient" (the brief this shim was built against) is a whole-root
433
+ * orientation primitive, not a per-locator one — a step that calls
434
+ * `page.ariaSnapshot()` directly is shim-specific and won't type-check
435
+ * against a bare `Page` under real Playwright; prefer
436
+ * `page.locator(':scope').ariaSnapshot()` (or any real Locator) in a spec
437
+ * meant to run unmodified under both hosts.
438
+ */
439
+ export type PageShim = Page & { ariaSnapshot(): Promise<string> };
440
+
441
+ /**
442
+ * Builds an in-page `Page` shim rooted at `root` (the game container — never
443
+ * editor chrome, see this module's own doc comment). Every member not
444
+ * implemented here throws `PageShimUnsupportedError` the instant it is
445
+ * touched (property read), naming itself and the supported list.
446
+ */
447
+ export function createPageShim(root: HTMLElement): PageShim {
448
+ const pageObj = {
449
+ keyboard: makeKeyboard(root),
450
+ evaluate: async (fn: unknown, arg?: unknown): Promise<unknown> => evaluateImpl(fn, arg),
451
+ locator: (selector: string): Locator =>
452
+ makeLocator(
453
+ () => Array.from(root.querySelectorAll(selector)),
454
+ `locator(${JSON.stringify(selector)})`,
455
+ ),
456
+ getByRole: (role: string, opts?: { name?: string | RegExp }): Locator =>
457
+ makeLocator(
458
+ () => queryByRole(root, role, opts),
459
+ `getByRole(${JSON.stringify(role)}${
460
+ opts?.name !== undefined ? `, {name: ${JSON.stringify(String(opts.name))}}` : ''
461
+ })`,
462
+ ),
463
+ getByText: (text: string | RegExp): Locator =>
464
+ makeLocator(() => queryByText(root, text), `getByText(${JSON.stringify(String(text))})`),
465
+ getByTestId: (testId: string | RegExp): Locator =>
466
+ makeLocator(
467
+ () => queryByTestId(root, testId),
468
+ `getByTestId(${JSON.stringify(String(testId))})`,
469
+ ),
470
+ click: async (selector: string): Promise<void> => {
471
+ await pageObj.locator(selector).click();
472
+ },
473
+ fill: async (selector: string, value: string): Promise<void> => {
474
+ await pageObj.locator(selector).fill(value);
475
+ },
476
+ ariaSnapshot: async (): Promise<string> => buildAriaSnapshot(root),
477
+ };
478
+ return wrapWithUnsupportedProxy(pageObj, 'Page') as unknown as PageShim;
479
+ }