@dreamboard-games/cli 0.1.30-alpha.3 → 0.1.30-alpha.31

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