@dreamboard-games/cli 0.1.30-alpha.12 → 0.1.30-alpha.13

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 (143) hide show
  1. package/README.md +2 -6
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs +18 -17
  3. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
  4. package/dist/agent-verifier/{chunk-TLYGTHXU.mjs → chunk-5GCZZ6NW.mjs} +3 -3
  5. package/dist/agent-verifier/{chunk-YR664DJX.mjs → chunk-A67WUYN2.mjs} +42 -68
  6. package/dist/agent-verifier/chunk-A67WUYN2.mjs.map +1 -0
  7. package/dist/agent-verifier/chunk-AXXUGU7Q.mjs +255 -0
  8. package/dist/agent-verifier/chunk-AXXUGU7Q.mjs.map +1 -0
  9. package/dist/agent-verifier/chunk-CO3BRUD6.mjs +342 -0
  10. package/dist/agent-verifier/chunk-CO3BRUD6.mjs.map +1 -0
  11. package/dist/agent-verifier/chunk-DPYC2NDB.mjs +59 -0
  12. package/dist/agent-verifier/chunk-DPYC2NDB.mjs.map +1 -0
  13. package/dist/agent-verifier/{chunk-4GU3PCHV.mjs → chunk-DWLTCUUX.mjs} +576 -393
  14. package/dist/agent-verifier/chunk-DWLTCUUX.mjs.map +1 -0
  15. package/dist/agent-verifier/{chunk-COB56ESI.mjs → chunk-G2ECODRB.mjs} +2 -2
  16. package/dist/agent-verifier/{chunk-6XRC5PWB.mjs → chunk-H3XNWKJU.mjs} +217 -232
  17. package/dist/agent-verifier/chunk-H3XNWKJU.mjs.map +1 -0
  18. package/dist/agent-verifier/{chunk-Z6OZWUIZ.mjs → chunk-HLHT57AW.mjs} +64 -16
  19. package/dist/agent-verifier/chunk-HLHT57AW.mjs.map +1 -0
  20. package/dist/agent-verifier/{chunk-YDIOW2BO.mjs → chunk-INIK6LHK.mjs} +2 -2
  21. package/dist/agent-verifier/{chunk-VLOIZDR6.mjs → chunk-JPN62WDY.mjs} +199 -190
  22. package/dist/agent-verifier/chunk-JPN62WDY.mjs.map +1 -0
  23. package/dist/agent-verifier/{chunk-UWJIZML3.mjs → chunk-LKQ557TJ.mjs} +30 -23
  24. package/dist/agent-verifier/chunk-LKQ557TJ.mjs.map +1 -0
  25. package/dist/agent-verifier/{chunk-NAK77WXW.mjs → chunk-MYMVXTZT.mjs} +4 -5
  26. package/dist/agent-verifier/chunk-MYMVXTZT.mjs.map +1 -0
  27. package/dist/agent-verifier/{chunk-UIJ2NDG6.mjs → chunk-NFL3Z4Z7.mjs} +31 -238
  28. package/dist/agent-verifier/chunk-NFL3Z4Z7.mjs.map +1 -0
  29. package/dist/agent-verifier/{chunk-XKCJBIRY.mjs → chunk-QD4SQNUP.mjs} +2 -2
  30. package/dist/agent-verifier/{chunk-IAYRNVUC.mjs → chunk-RDYXWXXC.mjs} +1 -3
  31. package/dist/agent-verifier/{chunk-QBAF7EYR.mjs → chunk-TTB7AIHZ.mjs} +4 -4
  32. package/dist/agent-verifier/{chunk-QBAF7EYR.mjs.map → chunk-TTB7AIHZ.mjs.map} +1 -1
  33. package/dist/agent-verifier/chunk-V6AQDR7W.mjs +89 -0
  34. package/dist/agent-verifier/chunk-V6AQDR7W.mjs.map +1 -0
  35. package/dist/agent-verifier/{chunk-RHI6S4SU.mjs → chunk-V7ABTZXW.mjs} +1 -3
  36. package/dist/agent-verifier/{chunk-RHI6S4SU.mjs.map → chunk-V7ABTZXW.mjs.map} +1 -1
  37. package/dist/agent-verifier/chunk-WAFBU5U7.mjs +467 -0
  38. package/dist/agent-verifier/chunk-WAFBU5U7.mjs.map +1 -0
  39. package/dist/agent-verifier/{chunk-3IJBOLGT.mjs → chunk-WSIYUUSD.mjs} +2 -2
  40. package/dist/agent-verifier/{compile-WZ7X6I2A.mjs → compile-H6KCBCVH.mjs} +22 -18
  41. package/dist/agent-verifier/compile-H6KCBCVH.mjs.map +1 -0
  42. package/dist/agent-verifier/{global-config-XHL7BCKN.mjs → global-config-6UGFPLDA.mjs} +4 -3
  43. package/dist/agent-verifier/{keychain-backend-A3MRWLPF.mjs → keychain-backend-BQLW5VEC.mjs} +11 -6
  44. package/dist/agent-verifier/keychain-backend-BQLW5VEC.mjs.map +1 -0
  45. package/dist/agent-verifier/{local-files-ZW52HSVT.mjs → local-files-WPHUV6GU.mjs} +6 -6
  46. package/dist/agent-verifier/{materialize-workspace-BKZLLFI4.mjs → materialize-workspace-EHCQB4UU.mjs} +17 -17
  47. package/dist/agent-verifier/materialize-workspace-EHCQB4UU.mjs.map +1 -0
  48. package/dist/agent-verifier/{reducer-bundle-preflight-7NYZF5ZT.mjs → reducer-bundle-preflight-3DSXIELT.mjs} +4 -4
  49. package/dist/agent-verifier/reducer-contract-preflight-FQB7M4PU.mjs +11 -0
  50. package/dist/agent-verifier/{reducer-native-test-harness-D4VWPIAC.mjs → reducer-native-test-harness-GY2CCQWN.mjs} +12 -9
  51. package/dist/agent-verifier/{static-scaffold-JCRBDKEH.mjs → static-scaffold-3O543YTZ.mjs} +7 -9
  52. package/dist/agent-verifier/{sync-ELLJEWMB.mjs → sync-URBFMM6H.mjs} +24 -22
  53. package/dist/agent-verifier/{sync-ELLJEWMB.mjs.map → sync-URBFMM6H.mjs.map} +1 -1
  54. package/dist/agent-verifier/{test-OSXBPLSP.mjs → test-LQAGEQLY.mjs} +19 -17
  55. package/dist/agent-verifier/test-LQAGEQLY.mjs.map +1 -0
  56. package/dist/agent-verifier/{workspace-codegen-WPZHMATU.mjs → workspace-codegen-4IWICKLB.mjs} +3 -3
  57. package/dist/agent-verifier/{workspace-dependencies-ULZZZPNX.mjs → workspace-dependencies-ZMHPHVQV.mjs} +2 -2
  58. package/dist/authoring-compatibility-internal.js +12 -0
  59. package/dist/{agent-verifier/chunk-W2MDP5ZN.mjs → chunk-AVOAT522.js} +118 -21
  60. package/dist/chunk-AVOAT522.js.map +1 -0
  61. package/dist/chunk-EV7Q6BIF.js +4298 -0
  62. package/dist/chunk-EV7Q6BIF.js.map +1 -0
  63. package/dist/chunk-FFO2IJL3.js +204 -0
  64. package/dist/chunk-FFO2IJL3.js.map +1 -0
  65. package/dist/{chunk-P5TITCD3.js → chunk-GS6A7T53.js} +2240 -4554
  66. package/dist/chunk-GS6A7T53.js.map +1 -0
  67. package/dist/{global-config-WPJRXVDO.js → global-config-NLGAFSRU.js} +3 -2
  68. package/dist/global-config-NLGAFSRU.js.map +1 -0
  69. package/dist/index.js +1371 -3545
  70. package/dist/index.js.map +1 -1
  71. package/dist/internal.js +14 -8
  72. package/dist/{keychain-backend-JHTXAKWC.js → keychain-backend-47LZ5IX5.js} +11 -6
  73. package/dist/keychain-backend-47LZ5IX5.js.map +1 -0
  74. package/package.json +9 -19
  75. package/release/authoring-release-set.json +38 -0
  76. package/skills/dreamboard/references/manifest-authoring.md +11 -3
  77. package/dist/agent-verifier/chunk-4GU3PCHV.mjs.map +0 -1
  78. package/dist/agent-verifier/chunk-6XRC5PWB.mjs.map +0 -1
  79. package/dist/agent-verifier/chunk-G42BGGG2.mjs +0 -70
  80. package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +0 -1
  81. package/dist/agent-verifier/chunk-KK47X7RV.mjs +0 -14
  82. package/dist/agent-verifier/chunk-KK47X7RV.mjs.map +0 -1
  83. package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +0 -1
  84. package/dist/agent-verifier/chunk-UIJ2NDG6.mjs.map +0 -1
  85. package/dist/agent-verifier/chunk-UWJIZML3.mjs.map +0 -1
  86. package/dist/agent-verifier/chunk-VLOIZDR6.mjs.map +0 -1
  87. package/dist/agent-verifier/chunk-W2MDP5ZN.mjs.map +0 -1
  88. package/dist/agent-verifier/chunk-YR664DJX.mjs.map +0 -1
  89. package/dist/agent-verifier/chunk-Z6OZWUIZ.mjs.map +0 -1
  90. package/dist/agent-verifier/compile-WZ7X6I2A.mjs.map +0 -1
  91. package/dist/agent-verifier/keychain-backend-A3MRWLPF.mjs.map +0 -1
  92. package/dist/agent-verifier/materialize-workspace-BKZLLFI4.mjs.map +0 -1
  93. package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs +0 -11
  94. package/dist/agent-verifier/test-OSXBPLSP.mjs.map +0 -1
  95. package/dist/chunk-GXM7RRZJ.js +0 -433
  96. package/dist/chunk-GXM7RRZJ.js.map +0 -1
  97. package/dist/chunk-P5TITCD3.js.map +0 -1
  98. package/dist/dev-host/components/drawer.tsx +0 -132
  99. package/dist/dev-host/components/input.tsx +0 -21
  100. package/dist/dev-host/dev-api-proxy-plugin.ts +0 -328
  101. package/dist/dev-host/dev-author-dom-warnings.ts +0 -100
  102. package/dist/dev-host/dev-diagnostics.ts +0 -62
  103. package/dist/dev-host/dev-fallback-stylesheet.ts +0 -53
  104. package/dist/dev-host/dev-hmr-guard-plugin.ts +0 -47
  105. package/dist/dev-host/dev-host-controller.ts +0 -674
  106. package/dist/dev-host/dev-host-player-query.ts +0 -17
  107. package/dist/dev-host/dev-host-session-transport.ts +0 -52
  108. package/dist/dev-host/dev-host-storage.ts +0 -56
  109. package/dist/dev-host/dev-log-relay-plugin.ts +0 -510
  110. package/dist/dev-host/dev-runtime-config.ts +0 -14
  111. package/dist/dev-host/dev-runtime-platform.ts +0 -335
  112. package/dist/dev-host/dev-virtual-modules-plugin.ts +0 -64
  113. package/dist/dev-host/host-main.css +0 -224
  114. package/dist/dev-host/host-main.tsx +0 -954
  115. package/dist/dev-host/index.html +0 -56
  116. package/dist/dev-host/lib/utils.ts +0 -6
  117. package/dist/dev-host/plugin-main.ts +0 -61
  118. package/dist/dev-host/plugin.html +0 -24
  119. package/dist/dev-host/shared-styles.css +0 -144
  120. package/dist/dev-host/start-dev-server.ts +0 -140
  121. package/dist/dev-host/virtual-modules.d.ts +0 -27
  122. package/dist/keychain-backend-JHTXAKWC.js.map +0 -1
  123. /package/dist/agent-verifier/{chunk-TLYGTHXU.mjs.map → chunk-5GCZZ6NW.mjs.map} +0 -0
  124. /package/dist/agent-verifier/{chunk-COB56ESI.mjs.map → chunk-G2ECODRB.mjs.map} +0 -0
  125. /package/dist/agent-verifier/{chunk-YDIOW2BO.mjs.map → chunk-INIK6LHK.mjs.map} +0 -0
  126. /package/dist/agent-verifier/{chunk-XKCJBIRY.mjs.map → chunk-QD4SQNUP.mjs.map} +0 -0
  127. /package/dist/agent-verifier/{chunk-IAYRNVUC.mjs.map → chunk-RDYXWXXC.mjs.map} +0 -0
  128. /package/dist/agent-verifier/{chunk-3IJBOLGT.mjs.map → chunk-WSIYUUSD.mjs.map} +0 -0
  129. /package/dist/agent-verifier/{global-config-XHL7BCKN.mjs.map → global-config-6UGFPLDA.mjs.map} +0 -0
  130. /package/dist/agent-verifier/{local-files-ZW52HSVT.mjs.map → local-files-WPHUV6GU.mjs.map} +0 -0
  131. /package/dist/agent-verifier/{reducer-bundle-preflight-7NYZF5ZT.mjs.map → reducer-bundle-preflight-3DSXIELT.mjs.map} +0 -0
  132. /package/dist/agent-verifier/{reducer-contract-preflight-COD2CO22.mjs.map → reducer-contract-preflight-FQB7M4PU.mjs.map} +0 -0
  133. /package/dist/agent-verifier/{reducer-native-test-harness-D4VWPIAC.mjs.map → reducer-native-test-harness-GY2CCQWN.mjs.map} +0 -0
  134. /package/dist/agent-verifier/{static-scaffold-JCRBDKEH.mjs.map → static-scaffold-3O543YTZ.mjs.map} +0 -0
  135. /package/dist/agent-verifier/{workspace-codegen-WPZHMATU.mjs.map → workspace-codegen-4IWICKLB.mjs.map} +0 -0
  136. /package/dist/agent-verifier/{workspace-dependencies-ULZZZPNX.mjs.map → workspace-dependencies-ZMHPHVQV.mjs.map} +0 -0
  137. /package/dist/{global-config-WPJRXVDO.js.map → authoring-compatibility-internal.js.map} +0 -0
  138. /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.framework.json +0 -0
  139. /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.json +0 -0
  140. /package/{dist/scaffold → scaffold}/assets/static/ui/index.tsx +0 -0
  141. /package/{dist/scaffold → scaffold}/assets/static/ui/style.css +0 -0
  142. /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.framework.json +0 -0
  143. /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.json +0 -0
@@ -1,954 +0,0 @@
1
- /// <reference lib="dom" />
2
-
3
- import "./host-main.css";
4
- import { useEffect, useState } from "react";
5
- import { createRoot } from "react-dom/client";
6
- import {
7
- AlertTriangle,
8
- Check,
9
- ChevronDown,
10
- Copy,
11
- RotateCw,
12
- X,
13
- } from "lucide-react";
14
- import {
15
- Drawer,
16
- DrawerClose,
17
- DrawerContent,
18
- DrawerDescription,
19
- DrawerHeader,
20
- DrawerTitle,
21
- DrawerTrigger,
22
- } from "./components/drawer.js";
23
- import { Input } from "./components/input.js";
24
- import { createGameplayCapability } from "@dreamboard-games/api-client";
25
- import { client } from "@dreamboard-games/api-client/client.gen";
26
- import {
27
- HostFeedbackToaster,
28
- HostHistoryNavigator,
29
- HostPlayerSwitcher,
30
- HostSessionMetadata,
31
- PerfOverlay,
32
- type HostControllablePlayer,
33
- } from "@dreamboard-games/ui-host-runtime/components";
34
- import {
35
- LongPollSessionManager,
36
- createGameplayAuthorityTransport,
37
- createUnifiedSessionStore,
38
- unifiedSessionSelectors,
39
- type HistoryState,
40
- type HostFeedback,
41
- } from "@dreamboard-games/ui-host-runtime/runtime";
42
- import devConfig from "virtual:dreamboard-dev-config";
43
- import {
44
- createDevDiagnosticsLogger,
45
- formatConsoleArgs,
46
- resolveDevDiagnosticsLevel,
47
- shouldRelayDevLog,
48
- stringifyForRelay,
49
- type DevLogEnvelope,
50
- } from "./dev-diagnostics.js";
51
- import {
52
- DevHostController,
53
- type DevHostRuntimeError,
54
- } from "./dev-host-controller.js";
55
- import { createDevHostSessionTransport } from "./dev-host-session-transport.js";
56
- import {
57
- SessionStorageDevHostStorage,
58
- type ActiveSession,
59
- } from "./dev-host-storage.js";
60
- import { resolveInitialDevHostPlayerId } from "./dev-host-player-query.js";
61
-
62
- const diagnosticsLevel = resolveDevDiagnosticsLevel(devConfig.debug);
63
- const devLogger = createDevDiagnosticsLogger(diagnosticsLevel);
64
- const storage = new SessionStorageDevHostStorage(window.sessionStorage);
65
- // The browser never sees the bearer token. All backend traffic is
66
- // same-origin and the CLI's reverse-proxy middleware (`/api/*`) injects
67
- // `Authorization: Bearer <fresh>` on the wire.
68
- client.setConfig({ baseUrl: "" });
69
- const createDevHostGameplayCapability = (
70
- options: Parameters<typeof createGameplayCapability>[0],
71
- ): ReturnType<typeof createGameplayCapability> =>
72
- createGameplayCapability({ ...options, client });
73
- // Gameplay streaming and submits go through the Gameplay Authority WebSocket
74
- // (the backend's event-batches stream is removed); snapshot/start/dev-session
75
- // requests fall back to the dev-server endpoints.
76
- const hostSessionTransport = createGameplayAuthorityTransport({
77
- capabilityRequester: createDevHostGameplayCapability,
78
- fallbackTransport: createDevHostSessionTransport(),
79
- getCurrentSessionContext: () =>
80
- unifiedSessionSelectors.sessionContext(store.getState()),
81
- getCurrentGameplay: () =>
82
- unifiedSessionSelectors.gameplayViewport(store.getState()),
83
- });
84
- let runtimeDisposed = false;
85
-
86
- type DevAuthorWarning = {
87
- id: string;
88
- title: string;
89
- message: string;
90
- source: DevLogEnvelope["source"];
91
- };
92
-
93
- let devAuthorWarnings: DevAuthorWarning[] = [];
94
-
95
- const store = createUnifiedSessionStore({
96
- createSseManager: () =>
97
- new LongPollSessionManager({
98
- transport: hostSessionTransport,
99
- logger: {
100
- log: (...args: unknown[]) => {
101
- if (runtimeDisposed) {
102
- return;
103
- }
104
- devLogger.log(...args);
105
- },
106
- warn: (...args: unknown[]) => {
107
- if (runtimeDisposed) {
108
- return;
109
- }
110
- devLogger.warn(...args);
111
- },
112
- error: (...args: unknown[]) => {
113
- if (runtimeDisposed) {
114
- return;
115
- }
116
- devLogger.error(...args);
117
- controller.handleSseTransportError(args);
118
- },
119
- },
120
- }),
121
- logger: devLogger,
122
- transport: hostSessionTransport,
123
- fallbackToAllSeatsWhenUserIdMissing: !devConfig.userId,
124
- });
125
-
126
- const controller = new DevHostController(
127
- store,
128
- storage,
129
- {
130
- autoStartGame: devConfig.autoStartGame,
131
- compiledResultId: devConfig.compiledResultId,
132
- createDevSessionSnapshot: hostSessionTransport.createDevSessionSnapshot,
133
- debug: devConfig.debug,
134
- fallbackSession: devConfig.initialSession,
135
- gameId: devConfig.gameId,
136
- initialPlayerId: resolveInitialDevHostPlayerId(window.location.search),
137
- playerCount: devConfig.playerCount,
138
- setupProfileId: devConfig.setupProfileId,
139
- slug: devConfig.slug,
140
- userId: devConfig.userId,
141
- },
142
- devLogger,
143
- );
144
-
145
- installProxyAuthErrorInterceptor();
146
-
147
- const app = document.getElementById("app");
148
- if (!(app instanceof HTMLElement)) {
149
- throw new Error("Missing root app container.");
150
- }
151
- const root = createRoot(app);
152
-
153
- function installProxyAuthErrorInterceptor(): void {
154
- client.interceptors.response.use(async (response) => {
155
- if (response.status !== 401) {
156
- return response;
157
- }
158
- const contentType = response.headers.get("content-type") ?? "";
159
- if (!contentType.includes("application/json")) {
160
- return response;
161
- }
162
- // Clone first so downstream handlers can still read the body.
163
- const clone = response.clone();
164
- type AuthErrorPayload = { error?: unknown; message?: unknown };
165
- let payload: AuthErrorPayload | null = null;
166
- try {
167
- payload = (await clone.json()) as AuthErrorPayload;
168
- } catch {
169
- return response;
170
- }
171
- if (payload?.error !== "session_invalid") {
172
- return response;
173
- }
174
- const detail =
175
- typeof payload.message === "string" && payload.message.length > 0
176
- ? payload.message
177
- : "Stored Dreamboard session is no longer valid.";
178
- controller.reportRuntimeError({
179
- title: "Session expired",
180
- summary: `${detail} Run \`dreamboard login\` in your terminal, then reload this page.`,
181
- violations: [],
182
- });
183
- return response;
184
- });
185
- }
186
-
187
- const restoreConsoleRelay = installConsoleRelay("host");
188
- const removeWindowErrorRelay = installWindowErrorRelay("host");
189
- installSseRelay();
190
- window.addEventListener("message", handlePluginLogMessage);
191
- window.addEventListener("pagehide", disposeHostRuntime);
192
- window.addEventListener("beforeunload", disposeHostRuntime);
193
-
194
- const unsubscribeStoreRender = store.subscribe(() => {
195
- render();
196
- });
197
- const unsubscribeControllerRender = controller.subscribe(() => {
198
- render();
199
- });
200
-
201
- void controller.initialize();
202
-
203
- if (import.meta.hot) {
204
- import.meta.hot.accept();
205
- import.meta.hot.dispose(() => {
206
- disposeHostRuntime();
207
- });
208
- }
209
-
210
- function render(): void {
211
- if (runtimeDisposed) {
212
- return;
213
- }
214
-
215
- const state = store.getState();
216
- const controllerState = controller.getSnapshot();
217
- const session = state.getPluginSnapshot().session;
218
- const seats = unifiedSessionSelectors.seats(state);
219
- const controllablePlayers: HostControllablePlayer[] =
220
- session.controllablePlayerIds.map((playerId) => {
221
- const seat = seats.find((entry) => entry.playerId === playerId);
222
- return {
223
- playerId,
224
- displayName: seat?.displayName || playerId,
225
- };
226
- });
227
- const isHost = unifiedSessionSelectors.isSessionHost(state);
228
- const phase = unifiedSessionSelectors.sessionType(state);
229
-
230
- root.render(
231
- <DevHostApp
232
- session={controllerState.session}
233
- phase={phase}
234
- bootstrapStatus={unifiedSessionSelectors.bootstrapStatus(state)}
235
- isConnected={unifiedSessionSelectors.isConnected(state)}
236
- connectionError={unifiedSessionSelectors.connectionError(state)}
237
- runtimeError={controllerState.runtimeError}
238
- syncId={unifiedSessionSelectors.syncId(state)}
239
- pluginReady={controllerState.pluginReady}
240
- controllablePlayers={controllablePlayers}
241
- controllingPlayerId={
242
- unifiedSessionSelectors.currentPlayerId(state) ??
243
- session.controllingPlayerId
244
- }
245
- canStart={phase === "lobby" && unifiedSessionSelectors.canStart(state)}
246
- isHost={isHost}
247
- history={unifiedSessionSelectors.history(state)}
248
- hostFeedback={unifiedSessionSelectors.hostFeedback(state)}
249
- authorWarnings={devAuthorWarnings}
250
- iframeSrc={controllerState.iframeSrc}
251
- seedValue={controllerState.seedValue}
252
- isCreatingSession={controllerState.isCreatingSession}
253
- onSeedChange={(value) => controller.setSeedValue(value)}
254
- onCreateSession={() => void controller.createNewSession()}
255
- onStartGame={() => void controller.startGameFromSidebar()}
256
- onSwitchPlayer={(playerId) => controller.switchPlayer(playerId)}
257
- onRestoreHistory={(entryId) => controller.restoreHistoryEntry(entryId)}
258
- onDismissHostFeedback={(feedbackId) =>
259
- store.getState().dismissHostFeedback(feedbackId)
260
- }
261
- onDismissAuthorWarning={dismissDevAuthorWarning}
262
- onDismissRuntimeError={() => controller.dismissRuntimeError()}
263
- onRetryBootstrap={() => void controller.initialize()}
264
- onIframeReady={(element) => {
265
- controller.setIframe(element);
266
- }}
267
- onIframeLoad={() => {
268
- controller.onIframeLoad();
269
- }}
270
- />,
271
- );
272
- }
273
-
274
- type DevHostAppProps = {
275
- session: ActiveSession;
276
- phase: string;
277
- bootstrapStatus: "loading" | "lobby" | "renderable" | "error";
278
- isConnected: boolean;
279
- connectionError: string | null;
280
- runtimeError: DevHostRuntimeError | null;
281
- syncId: number;
282
- pluginReady: boolean;
283
- controllablePlayers: HostControllablePlayer[];
284
- controllingPlayerId: string | null;
285
- canStart: boolean;
286
- isHost: boolean;
287
- history: HistoryState | null;
288
- hostFeedback: HostFeedback[];
289
- authorWarnings: DevAuthorWarning[];
290
- iframeSrc: string;
291
- seedValue: string;
292
- isCreatingSession: boolean;
293
- onSeedChange: (value: string) => void;
294
- onCreateSession: () => void;
295
- onStartGame: () => void;
296
- onSwitchPlayer: (playerId: string) => void;
297
- onRestoreHistory: (entryId: string) => Promise<void>;
298
- onDismissHostFeedback: (feedbackId: string) => void;
299
- onDismissAuthorWarning: (warningId: string) => void;
300
- onDismissRuntimeError: () => void;
301
- onRetryBootstrap: () => void;
302
- onIframeReady: (element: HTMLIFrameElement | null) => void;
303
- onIframeLoad: () => void;
304
- };
305
-
306
- function DevHostApp({
307
- session,
308
- phase,
309
- bootstrapStatus,
310
- isConnected,
311
- connectionError,
312
- runtimeError,
313
- syncId,
314
- pluginReady,
315
- controllablePlayers,
316
- controllingPlayerId,
317
- canStart,
318
- isHost,
319
- history,
320
- hostFeedback,
321
- authorWarnings,
322
- iframeSrc,
323
- seedValue,
324
- isCreatingSession,
325
- onSeedChange,
326
- onCreateSession,
327
- onStartGame,
328
- onSwitchPlayer,
329
- onRestoreHistory,
330
- onDismissHostFeedback,
331
- onDismissAuthorWarning,
332
- onDismissRuntimeError,
333
- onRetryBootstrap,
334
- onIframeReady,
335
- onIframeLoad,
336
- }: DevHostAppProps) {
337
- const [isSidebarOpen, setIsSidebarOpen] = useState(() =>
338
- storage.loadSidebarOpen(),
339
- );
340
- const needsBootstrap = phase !== "error" && bootstrapStatus === "loading";
341
-
342
- const handleToggleSidebar = (open: boolean) => {
343
- setIsSidebarOpen(open);
344
- storage.persistSidebarOpen(open);
345
- };
346
-
347
- // ⌘. / Ctrl+. toggles the dev drawer. Cheap, doesn't fight typical
348
- // game-side keybindings, and discoverable via the edge-tab tooltip.
349
- useEffect(() => {
350
- const handler = (event: KeyboardEvent) => {
351
- if (!(event.metaKey || event.ctrlKey) || event.key !== ".") {
352
- return;
353
- }
354
- event.preventDefault();
355
- handleToggleSidebar(!isSidebarOpen);
356
- };
357
- window.addEventListener("keydown", handler);
358
- return () => window.removeEventListener("keydown", handler);
359
- // eslint-disable-next-line react-hooks/exhaustive-deps
360
- }, [isSidebarOpen]);
361
-
362
- const primaryAction = canStart
363
- ? { label: "Start Game", handler: onStartGame, disabled: false }
364
- : {
365
- label: isCreatingSession ? "Creating…" : "New Session",
366
- handler: onCreateSession,
367
- disabled: isCreatingSession,
368
- };
369
-
370
- return (
371
- <div className="relative flex h-full w-full overflow-hidden bg-transparent font-sans text-foreground">
372
- <HostFeedbackToaster
373
- feedback={hostFeedback}
374
- onDismiss={onDismissHostFeedback}
375
- />
376
- <DevAuthorWarningPanel
377
- warnings={authorWarnings}
378
- onDismiss={onDismissAuthorWarning}
379
- />
380
- <main className="absolute inset-0 z-0 flex flex-col bg-transparent">
381
- <iframe
382
- ref={onIframeReady}
383
- src={iframeSrc}
384
- referrerPolicy="no-referrer"
385
- title="Dreamboard UI Plugin"
386
- className="h-full w-full flex-1 border-0 bg-background"
387
- onLoad={onIframeLoad}
388
- />
389
- {needsBootstrap ? (
390
- <div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-[#fdfbf7]/92 px-6 text-center backdrop-blur-[2px]">
391
- <div className="max-w-md rounded-2xl border border-border/30 bg-white px-5 py-4 shadow-[0_24px_60px_-12px_rgba(45,45,45,0.25),0_8px_20px_-8px_rgba(45,45,45,0.18)]">
392
- <p className="text-[10px] font-bold uppercase tracking-[0.18em] text-muted-foreground">
393
- Dev host
394
- </p>
395
- <h2 className="mt-2 font-display text-xl text-foreground">
396
- Waiting for session bootstrap
397
- </h2>
398
- <p className="mt-3 text-sm font-medium text-foreground">
399
- Attaching to{" "}
400
- <span className="font-bold">{session.shortCode}</span>.
401
- </p>
402
- <p className="mt-2 text-sm text-muted-foreground">
403
- The host will create a fresh session automatically if the
404
- current one cannot finish bootstrapping.
405
- </p>
406
- {connectionError ? (
407
- <p className="mt-3 rounded-md border border-border/30 bg-[#ffe1d6] px-3 py-2 text-sm font-semibold text-foreground">
408
- {connectionError}
409
- </p>
410
- ) : null}
411
- </div>
412
- </div>
413
- ) : null}
414
- {runtimeError ? (
415
- <RuntimeErrorOverlay
416
- error={runtimeError}
417
- onDismiss={onDismissRuntimeError}
418
- onRetry={onRetryBootstrap}
419
- />
420
- ) : null}
421
- </main>
422
-
423
- <Drawer
424
- open={isSidebarOpen}
425
- onOpenChange={handleToggleSidebar}
426
- direction="left"
427
- >
428
- {!isSidebarOpen ? (
429
- <DrawerTrigger asChild>
430
- <button
431
- type="button"
432
- aria-label="Open Dev Tools (⌘.)"
433
- title="Open Dev Tools (⌘.)"
434
- className="dev-edge-handle group fixed left-0 top-0 z-40 flex h-full w-4 cursor-pointer items-center justify-start"
435
- >
436
- <span className="dev-edge-strip block h-24 w-[3px] rounded-r-full bg-border/25" />
437
- </button>
438
- </DrawerTrigger>
439
- ) : null}
440
-
441
- <DrawerContent className="h-full w-[340px] max-w-[calc(100vw-1rem)] border-r border-border/20 bg-[#fdfbf7] p-0 rounded-r-2xl shadow-[8px_0_32px_-8px_rgba(45,45,45,0.18),2px_0_8px_-2px_rgba(45,45,45,0.12)]">
442
- <div className="flex h-full flex-col overflow-hidden">
443
- <DrawerHeader className="flex flex-row items-center justify-between gap-3 px-6 pb-3 pt-6">
444
- <DrawerDescription className="sr-only">
445
- Developer controls for the local Dreamboard host session. Press
446
- ⌘ + period to toggle.
447
- </DrawerDescription>
448
- <DrawerTitle className="min-w-0 flex-1 truncate font-display text-2xl leading-tight text-foreground">
449
- {devConfig.slug}
450
- </DrawerTitle>
451
- <DrawerClose asChild>
452
- <button
453
- type="button"
454
- className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground"
455
- title="Hide Dev Tools (⌘.)"
456
- aria-label="Hide Dev Tools (⌘.)"
457
- >
458
- <X className="h-4 w-4" />
459
- </button>
460
- </DrawerClose>
461
- </DrawerHeader>
462
-
463
- <div className="flex-1 overflow-y-auto px-6 pb-6 pt-2">
464
- <ShortCodeRow shortCode={session.shortCode} />
465
-
466
- <SectionHeading className="mt-7">Play as</SectionHeading>
467
- <div className="mt-2.5">
468
- <HostPlayerSwitcher
469
- controllablePlayers={controllablePlayers}
470
- controllingPlayerId={controllingPlayerId}
471
- onSwitchPlayer={onSwitchPlayer}
472
- className="w-full min-w-0"
473
- />
474
- </div>
475
- <div className="dev-history-shell mt-2 flex justify-start">
476
- <HostHistoryNavigator
477
- isHost={isHost}
478
- history={history}
479
- onRestoreHistory={onRestoreHistory}
480
- />
481
- </div>
482
-
483
- <SectionHeading className="mt-7">Seed</SectionHeading>
484
- <Input
485
- id="seed-input"
486
- type="number"
487
- inputMode="numeric"
488
- className="dev-seed-input mt-2.5 h-10 text-sm"
489
- value={seedValue}
490
- onChange={(event) => onSeedChange(event.target.value)}
491
- />
492
- <button
493
- type="button"
494
- className="dev-calm-button mt-3 w-full"
495
- disabled={primaryAction.disabled}
496
- onClick={primaryAction.handler}
497
- >
498
- {primaryAction.label}
499
- </button>
500
-
501
- <details className="dev-debug-fold group/debug mt-7">
502
- <summary className="flex cursor-pointer list-none select-none items-center gap-1.5 text-[10px] font-bold uppercase tracking-[0.16em] text-muted-foreground transition-colors hover:text-foreground group-open/debug:text-foreground">
503
- <ChevronDown className="h-3.5 w-3.5 -rotate-90 transition-transform group-open/debug:rotate-0" />
504
- Debug
505
- </summary>
506
- <div className="dev-host-debug-shell mt-3 space-y-3 pb-1">
507
- <HostSessionMetadata
508
- gameId={session.gameId}
509
- sessionId={session.sessionId}
510
- shortCode={session.shortCode}
511
- />
512
- <DebugRow label="Backend" value={devConfig.apiBaseUrl} />
513
- </div>
514
- </details>
515
- </div>
516
- </div>
517
- </DrawerContent>
518
- </Drawer>
519
- <PerfOverlay />
520
- </div>
521
- );
522
- }
523
-
524
- function SectionHeading({
525
- className,
526
- children,
527
- }: {
528
- className?: string;
529
- children: React.ReactNode;
530
- }) {
531
- return (
532
- <p
533
- className={
534
- "text-[10px] font-bold uppercase tracking-[0.18em] text-muted-foreground" +
535
- (className ? ` ${className}` : "")
536
- }
537
- >
538
- {children}
539
- </p>
540
- );
541
- }
542
-
543
- function ShortCodeRow({ shortCode }: { shortCode: string }) {
544
- const [copied, setCopied] = useState(false);
545
-
546
- useEffect(() => {
547
- if (!copied) {
548
- return;
549
- }
550
- const timeoutId = window.setTimeout(() => setCopied(false), 1600);
551
- return () => window.clearTimeout(timeoutId);
552
- }, [copied]);
553
-
554
- const handleCopy = async () => {
555
- if (!shortCode || !navigator.clipboard?.writeText) {
556
- return;
557
- }
558
- try {
559
- await navigator.clipboard.writeText(shortCode);
560
- setCopied(true);
561
- } catch {
562
- // Clipboard failures are non-fatal.
563
- }
564
- };
565
-
566
- return (
567
- <div className="flex items-center justify-between gap-3">
568
- <span className="min-w-0 truncate font-mono text-base font-bold text-foreground">
569
- {shortCode}
570
- </span>
571
- <button
572
- type="button"
573
- onClick={handleCopy}
574
- className="shrink-0 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground"
575
- title={copied ? "Copied" : "Copy short code"}
576
- aria-label={copied ? "Copied" : "Copy short code"}
577
- >
578
- {copied ? (
579
- <Check className="h-3.5 w-3.5" />
580
- ) : (
581
- <Copy className="h-3.5 w-3.5" />
582
- )}
583
- </button>
584
- </div>
585
- );
586
- }
587
-
588
- function DebugRow({ label, value }: { label: string; value: string }) {
589
- return (
590
- <div className="flex items-start justify-between gap-3 text-xs">
591
- <span className="shrink-0 font-bold uppercase tracking-[0.14em] text-muted-foreground">
592
- {label}
593
- </span>
594
- <span className="min-w-0 break-all text-right font-mono text-foreground">
595
- {value}
596
- </span>
597
- </div>
598
- );
599
- }
600
-
601
- function DevAuthorWarningPanel({
602
- warnings,
603
- onDismiss,
604
- }: {
605
- warnings: DevAuthorWarning[];
606
- onDismiss: (warningId: string) => void;
607
- }) {
608
- if (warnings.length === 0) return null;
609
-
610
- return (
611
- <aside
612
- aria-label="Author warnings"
613
- className="pointer-events-none fixed right-4 top-4 z-30 flex w-[min(420px,calc(100vw-2rem))] flex-col gap-2"
614
- >
615
- {warnings.map((warning) => (
616
- <div
617
- key={warning.id}
618
- className="pointer-events-auto rounded-lg border-2 border-[#2d2d2d] bg-[#fff7d6] p-3 shadow-[4px_4px_0_#2d2d2d]"
619
- >
620
- <div className="flex items-start gap-3">
621
- <AlertTriangle
622
- className="mt-0.5 h-4 w-4 shrink-0 text-[#b45309]"
623
- aria-hidden="true"
624
- />
625
- <div className="min-w-0 flex-1">
626
- <p className="text-[10px] font-bold uppercase tracking-[0.18em] text-muted-foreground">
627
- Author warning · {warning.source}
628
- </p>
629
- <h2 className="mt-1 text-sm font-bold text-foreground">
630
- {warning.title}
631
- </h2>
632
- <p className="mt-1 whitespace-pre-wrap break-words text-xs font-medium leading-relaxed text-foreground/80">
633
- {warning.message}
634
- </p>
635
- </div>
636
- <button
637
- type="button"
638
- className="shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground"
639
- title="Dismiss author warning"
640
- aria-label="Dismiss author warning"
641
- onClick={() => onDismiss(warning.id)}
642
- >
643
- <X className="h-3.5 w-3.5" />
644
- </button>
645
- </div>
646
- </div>
647
- ))}
648
- </aside>
649
- );
650
- }
651
-
652
- /**
653
- * Full-viewport overlay that surfaces runtime failures (e.g. reducer
654
- * `initialize` rejections, session-start 500s) to the game author instead of
655
- * burying them in the backend log. Each violation is rendered as its own
656
- * block so the JS stack trace — which the backend already parses into a
657
- * separate violation entry — stays readable with file/line detail intact.
658
- */
659
- function RuntimeErrorOverlay({
660
- error,
661
- onDismiss,
662
- onRetry,
663
- }: {
664
- error: DevHostRuntimeError;
665
- onDismiss: () => void;
666
- onRetry: () => void;
667
- }) {
668
- const headlineViolation =
669
- error.violations.length > 0 ? error.violations[0] : null;
670
- const stackViolations = error.violations.slice(1);
671
-
672
- return (
673
- <div className="absolute inset-0 z-40 flex items-center justify-center bg-[#2d2d2d]/70 px-6 py-8 backdrop-blur-[2px]">
674
- <div className="relative max-h-[85vh] w-full max-w-2xl overflow-hidden rounded-lg border border-border bg-[#fff7e5] shadow-xl">
675
- <div className="flex items-start justify-between gap-4 border-b border-border bg-[#ffd3d3] px-5 py-4">
676
- <div>
677
- <p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-foreground/70">
678
- Runtime error
679
- </p>
680
- <h2 className="mt-1 text-xl font-semibold text-foreground">
681
- {error.title}
682
- </h2>
683
- </div>
684
- <button
685
- type="button"
686
- onClick={onDismiss}
687
- className="shrink-0 rounded-md border border-border bg-white p-1.5 transition-colors hover:bg-accent"
688
- title="Dismiss"
689
- >
690
- <X className="h-4 w-4" />
691
- </button>
692
- </div>
693
- <div className="max-h-[calc(85vh-88px)] overflow-y-auto px-5 py-4">
694
- <p className="text-sm font-medium text-foreground">{error.summary}</p>
695
- <div className="mt-4 flex flex-wrap gap-2">
696
- <button
697
- type="button"
698
- onClick={onRetry}
699
- className="inline-flex items-center gap-2 rounded-md border border-border bg-white px-3 py-2 text-sm font-semibold text-foreground transition-colors hover:bg-accent"
700
- >
701
- <RotateCw className="h-4 w-4" />
702
- Retry Bootstrap
703
- </button>
704
- </div>
705
- {headlineViolation ? (
706
- <div className="mt-4 rounded-md border border-border bg-white px-3 py-2">
707
- <p className="text-[10px] font-semibold uppercase tracking-[0.2em] text-muted-foreground">
708
- {headlineViolation.code ?? "message"}
709
- {headlineViolation.field ? ` · ${headlineViolation.field}` : ""}
710
- </p>
711
- <p className="mt-1 whitespace-pre-wrap break-words text-sm font-semibold text-foreground">
712
- {headlineViolation.message}
713
- </p>
714
- </div>
715
- ) : null}
716
- {stackViolations.length > 0 ? (
717
- <details className="mt-4" open>
718
- <summary className="cursor-pointer text-[11px] font-semibold uppercase tracking-[0.2em] text-foreground/70">
719
- Stack trace
720
- </summary>
721
- <pre className="mt-2 max-h-64 overflow-auto rounded-md border border-border bg-[#1f1f1f] p-3 text-[11px] leading-relaxed text-[#f8f8f2]">
722
- {stackViolations
723
- .map((violation) => violation.message)
724
- .join("\n")}
725
- </pre>
726
- </details>
727
- ) : null}
728
- {error.correlationId ? (
729
- <p className="mt-4 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
730
- Request ID · {error.correlationId}
731
- </p>
732
- ) : null}
733
- </div>
734
- </div>
735
- </div>
736
- );
737
- }
738
-
739
- function installSseRelay(): void {
740
- let lastLoggedEventId = 0;
741
- store.subscribe((state) => {
742
- const nextEntries = unifiedSessionSelectors
743
- .sseEvents(state)
744
- .filter((entry) => entry.id > lastLoggedEventId);
745
- if (nextEntries.length === 0) {
746
- return;
747
- }
748
-
749
- for (const entry of nextEntries) {
750
- lastLoggedEventId = entry.id;
751
- relayBrowserLog({
752
- source: "sse",
753
- level: "info",
754
- message: devConfig.debug
755
- ? `${entry.eventType} ${stringifyForRelay(entry.data)}`
756
- : `${entry.eventType} toUser=${getMessageRecipient(entry.data) ?? "-"}`,
757
- });
758
- }
759
- });
760
- }
761
-
762
- function getMessageRecipient(message: unknown): unknown {
763
- return message && typeof message === "object" && "toUser" in message
764
- ? (message as { toUser?: unknown }).toUser
765
- : null;
766
- }
767
-
768
- function installConsoleRelay(source: "host"): () => void {
769
- const original = {
770
- log: console.log.bind(console),
771
- warn: console.warn.bind(console),
772
- error: console.error.bind(console),
773
- };
774
-
775
- console.log = (...args: unknown[]) => {
776
- original.log(...args);
777
- relayBrowserLog({
778
- source,
779
- level: "log",
780
- message: formatConsoleArgs(args),
781
- });
782
- };
783
- console.warn = (...args: unknown[]) => {
784
- original.warn(...args);
785
- relayBrowserLog({
786
- source,
787
- level: "warn",
788
- message: formatConsoleArgs(args),
789
- });
790
- };
791
- console.error = (...args: unknown[]) => {
792
- original.error(...args);
793
- relayBrowserLog({
794
- source,
795
- level: "error",
796
- message: formatConsoleArgs(args),
797
- });
798
- };
799
-
800
- return () => {
801
- console.log = original.log;
802
- console.warn = original.warn;
803
- console.error = original.error;
804
- };
805
- }
806
-
807
- function installWindowErrorRelay(source: "host"): () => void {
808
- const onError = (event: ErrorEvent) => {
809
- if (runtimeDisposed || shouldIgnoreBrowserError(event.error)) {
810
- return;
811
- }
812
- relayBrowserLog({
813
- source,
814
- level: "error",
815
- message: `window.error ${event.message}`,
816
- });
817
- };
818
- const onUnhandledRejection = (event: PromiseRejectionEvent) => {
819
- if (runtimeDisposed || shouldIgnoreBrowserError(event.reason)) {
820
- return;
821
- }
822
- relayBrowserLog({
823
- source,
824
- level: "error",
825
- message: `unhandledrejection ${stringifyForRelay(event.reason)}`,
826
- });
827
- };
828
-
829
- window.addEventListener("error", onError);
830
- window.addEventListener("unhandledrejection", onUnhandledRejection);
831
-
832
- return () => {
833
- window.removeEventListener("error", onError);
834
- window.removeEventListener("unhandledrejection", onUnhandledRejection);
835
- };
836
- }
837
-
838
- function handlePluginLogMessage(event: MessageEvent): void {
839
- if (!controller.matchesPluginWindow(event.source)) {
840
- return;
841
- }
842
-
843
- const payload = event.data as Partial<DevLogEnvelope> & { type?: string };
844
- if (
845
- !payload ||
846
- typeof payload !== "object" ||
847
- payload.type !== "dreamboard-dev-console"
848
- ) {
849
- return;
850
- }
851
-
852
- relayBrowserLog({
853
- source: "plugin",
854
- level:
855
- payload.level === "warn" ||
856
- payload.level === "error" ||
857
- payload.level === "log"
858
- ? payload.level
859
- : "log",
860
- message:
861
- typeof payload.message === "string"
862
- ? payload.message
863
- : stringifyForRelay(payload.message),
864
- });
865
-
866
- if (payload.level === "error" && typeof payload.message === "string") {
867
- maybeAddDevAuthorWarning({
868
- source: "plugin",
869
- message: payload.message,
870
- });
871
- }
872
- }
873
-
874
- function relayBrowserLog(payload: DevLogEnvelope): void {
875
- if (runtimeDisposed || !shouldRelayDevLog(diagnosticsLevel, payload)) {
876
- return;
877
- }
878
-
879
- void fetch("/__dreamboard_dev/log", {
880
- method: "POST",
881
- headers: { "content-type": "application/json" },
882
- body: JSON.stringify(payload),
883
- keepalive: true,
884
- }).catch(() => {
885
- // Ignore log relay failures to avoid recursive console noise.
886
- });
887
- }
888
-
889
- function maybeAddDevAuthorWarning({
890
- source,
891
- message,
892
- }: {
893
- source: DevLogEnvelope["source"];
894
- message: string;
895
- }): void {
896
- const parsed = parseDevAuthorWarning(message);
897
- if (!parsed) return;
898
- const id = `${source}:${parsed.title}:${parsed.message}`;
899
- if (devAuthorWarnings.some((warning) => warning.id === id)) {
900
- return;
901
- }
902
- devAuthorWarnings = [
903
- { id, source, title: parsed.title, message: parsed.message },
904
- ...devAuthorWarnings,
905
- ].slice(0, 4);
906
- render();
907
- }
908
-
909
- function dismissDevAuthorWarning(warningId: string): void {
910
- const nextWarnings = devAuthorWarnings.filter(
911
- (warning) => warning.id !== warningId,
912
- );
913
- if (nextWarnings.length === devAuthorWarnings.length) {
914
- return;
915
- }
916
- devAuthorWarnings = nextWarnings;
917
- render();
918
- }
919
-
920
- function parseDevAuthorWarning(
921
- message: string,
922
- ): { title: string; message: string } | null {
923
- if (!message.startsWith("[dreamboard] ")) {
924
- return null;
925
- }
926
- if (message.includes("Ambiguous Board.")) {
927
- return {
928
- title: "Ambiguous board target",
929
- message,
930
- };
931
- }
932
- return null;
933
- }
934
-
935
- function disposeHostRuntime(): void {
936
- if (runtimeDisposed) {
937
- return;
938
- }
939
-
940
- runtimeDisposed = true;
941
- removeWindowErrorRelay();
942
- restoreConsoleRelay();
943
- window.removeEventListener("message", handlePluginLogMessage);
944
- window.removeEventListener("pagehide", disposeHostRuntime);
945
- window.removeEventListener("beforeunload", disposeHostRuntime);
946
- unsubscribeStoreRender();
947
- unsubscribeControllerRender();
948
- controller.dispose();
949
- root.unmount();
950
- }
951
-
952
- function shouldIgnoreBrowserError(value: unknown): boolean {
953
- return value instanceof Error && value.name === "AbortError";
954
- }