dreamboard 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EYYWGWTO.js → chunk-2RCUHMGL.js} +343 -1911
- package/dist/chunk-2RCUHMGL.js.map +1 -0
- package/dist/{chunk-MOVHYB6E.js → chunk-GN7232BY.js} +1025 -695
- package/dist/chunk-GN7232BY.js.map +1 -0
- package/dist/{chunk-BMYC6772.js → chunk-H3O43F5P.js} +9765 -3068
- package/dist/chunk-H3O43F5P.js.map +1 -0
- package/dist/dev-host/dev-api-proxy-plugin.ts +330 -0
- package/dist/dev-host/dev-diagnostics.ts +62 -0
- package/dist/dev-host/dev-fallback-stylesheet.ts +50 -0
- package/dist/dev-host/dev-host-controller.ts +686 -0
- package/dist/dev-host/dev-host-player-query.ts +17 -0
- package/dist/dev-host/dev-host-session-transport.ts +52 -0
- package/dist/dev-host/dev-host-storage.ts +56 -0
- package/dist/dev-host/dev-log-relay-plugin.ts +469 -0
- package/dist/dev-host/dev-runtime-config.ts +14 -0
- package/dist/dev-host/dev-runtime-platform.ts +419 -0
- package/dist/dev-host/dev-virtual-modules-plugin.ts +63 -0
- package/dist/dev-host/host-main.css +182 -0
- package/dist/dev-host/host-main.tsx +754 -0
- package/dist/dev-host/index.html +56 -0
- package/dist/dev-host/plugin-main.ts +55 -0
- package/dist/dev-host/plugin.html +24 -0
- package/dist/dev-host/start-dev-server.ts +138 -0
- package/dist/dev-host/virtual-modules.d.ts +27 -0
- package/dist/dist-FEPN3BDN.js +16543 -0
- package/dist/dist-FEPN3BDN.js.map +1 -0
- package/dist/{dist-WJRJNFLI.js → dist-FRURQI7Q.js} +4 -2
- package/dist/index.js +3 -3
- package/dist/internal.js +159 -124
- package/dist/internal.js.map +1 -1
- package/dist/runtime-packages/tailwind-config/shared-styles.css +146 -0
- package/dist/runtime-packages/ui/src/components/.gitkeep +0 -0
- package/dist/runtime-packages/ui/src/components/accordion.tsx +66 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/actions.tsx +65 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/artifact.tsx +147 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/branch.tsx +215 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/canvas.tsx +22 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/chain-of-thought.tsx +228 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/code-block.tsx +179 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/confirmation.tsx +158 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/connection.tsx +28 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/context.tsx +408 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/controls.tsx +18 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/conversation.tsx +97 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/edge.tsx +140 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/image.tsx +24 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/inline-citation.tsx +287 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/loader.tsx +96 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/message.tsx +463 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/node.tsx +71 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/panel.tsx +15 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/plan.tsx +142 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/prompt-input.tsx +1380 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/queue.tsx +274 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/reasoning.tsx +182 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/response.tsx +22 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/shimmer.tsx +64 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/sources.tsx +77 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/suggestion.tsx +53 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/task.tsx +87 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/tool.tsx +165 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/toolbar.tsx +16 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/web-preview.tsx +263 -0
- package/dist/runtime-packages/ui/src/components/alert-dialog.tsx +157 -0
- package/dist/runtime-packages/ui/src/components/alert.tsx +66 -0
- package/dist/runtime-packages/ui/src/components/avatar.tsx +53 -0
- package/dist/runtime-packages/ui/src/components/badge.tsx +46 -0
- package/dist/runtime-packages/ui/src/components/button-group.tsx +83 -0
- package/dist/runtime-packages/ui/src/components/button.tsx +65 -0
- package/dist/runtime-packages/ui/src/components/card.tsx +92 -0
- package/dist/runtime-packages/ui/src/components/carousel.tsx +241 -0
- package/dist/runtime-packages/ui/src/components/collapsible.tsx +33 -0
- package/dist/runtime-packages/ui/src/components/command.tsx +184 -0
- package/dist/runtime-packages/ui/src/components/context-menu.tsx +252 -0
- package/dist/runtime-packages/ui/src/components/counter.tsx +11 -0
- package/dist/runtime-packages/ui/src/components/dialog.tsx +146 -0
- package/dist/runtime-packages/ui/src/components/drawer.tsx +132 -0
- package/dist/runtime-packages/ui/src/components/dropdown-menu.tsx +257 -0
- package/dist/runtime-packages/ui/src/components/form.tsx +168 -0
- package/dist/runtime-packages/ui/src/components/header.tsx +13 -0
- package/dist/runtime-packages/ui/src/components/hover-card.tsx +44 -0
- package/dist/runtime-packages/ui/src/components/input-group.tsx +178 -0
- package/dist/runtime-packages/ui/src/components/input.tsx +21 -0
- package/dist/runtime-packages/ui/src/components/item.tsx +193 -0
- package/dist/runtime-packages/ui/src/components/label.tsx +24 -0
- package/dist/runtime-packages/ui/src/components/navigation-menu.tsx +168 -0
- package/dist/runtime-packages/ui/src/components/popover.tsx +55 -0
- package/dist/runtime-packages/ui/src/components/progress.tsx +31 -0
- package/dist/runtime-packages/ui/src/components/radio-group.tsx +45 -0
- package/dist/runtime-packages/ui/src/components/resizable.tsx +56 -0
- package/dist/runtime-packages/ui/src/components/scroll-area.tsx +58 -0
- package/dist/runtime-packages/ui/src/components/select.tsx +187 -0
- package/dist/runtime-packages/ui/src/components/separator.tsx +28 -0
- package/dist/runtime-packages/ui/src/components/skeleton.tsx +13 -0
- package/dist/runtime-packages/ui/src/components/sonner.tsx +25 -0
- package/dist/runtime-packages/ui/src/components/spinner.tsx +16 -0
- package/dist/runtime-packages/ui/src/components/switch.tsx +31 -0
- package/dist/runtime-packages/ui/src/components/table.tsx +137 -0
- package/dist/runtime-packages/ui/src/components/tabs.tsx +74 -0
- package/dist/runtime-packages/ui/src/components/textarea.tsx +23 -0
- package/dist/runtime-packages/ui/src/components/theme-provider.tsx +73 -0
- package/dist/runtime-packages/ui/src/components/tooltip.tsx +61 -0
- package/dist/runtime-packages/ui/src/components/ui/file-upload.tsx +195 -0
- package/dist/runtime-packages/ui/src/hooks/.gitkeep +0 -0
- package/dist/runtime-packages/ui/src/index.ts +68 -0
- package/dist/runtime-packages/ui/src/lib/utils.ts +6 -0
- package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +68 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +359 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +274 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +214 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +135 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +5 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +194 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +1 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +1 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +162 -0
- package/dist/runtime-packages/ui-host-runtime/src/index.ts +2 -0
- package/dist/runtime-packages/ui-host-runtime/src/logger.ts +11 -0
- package/dist/runtime-packages/ui-host-runtime/src/perf.ts +253 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +195 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +138 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +159 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +524 -0
- package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +12 -0
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +122 -0
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +26 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +477 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +209 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +112 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +318 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +211 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +814 -0
- package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +334 -0
- package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +180 -0
- package/dist/scaffold/assets/static/app/tsconfig.framework.json +3 -1
- package/dist/scaffold/assets/static/ui/tsconfig.framework.json +7 -19
- package/package.json +52 -17
- package/skills/dreamboard/SKILL.md +41 -75
- package/LICENSE +0 -89
- package/NOTICE +0 -1
- package/dist/chunk-BMYC6772.js.map +0 -1
- package/dist/chunk-EYYWGWTO.js.map +0 -1
- package/dist/chunk-MOVHYB6E.js.map +0 -1
- package/dist/scaffold/assets/static/ui/App.tsx +0 -22
- package/dist/src-CUL7EGGG.js +0 -634
- package/dist/src-CUL7EGGG.js.map +0 -1
- package/skills/dreamboard/references/authoring-lifecycle.md +0 -102
- package/skills/dreamboard/references/board-surfaces.md +0 -36
- package/skills/dreamboard/references/board-topology.md +0 -443
- package/skills/dreamboard/references/boards-and-topology.md +0 -100
- package/skills/dreamboard/references/card-actions.md +0 -107
- package/skills/dreamboard/references/cli.md +0 -120
- package/skills/dreamboard/references/core-concepts.md +0 -514
- package/skills/dreamboard/references/custom-renderers.md +0 -26
- package/skills/dreamboard/references/derived-values.md +0 -55
- package/skills/dreamboard/references/effects.md +0 -111
- package/skills/dreamboard/references/game-contract.md +0 -89
- package/skills/dreamboard/references/game-definition.md +0 -89
- package/skills/dreamboard/references/game-shell.md +0 -80
- package/skills/dreamboard/references/hand-surfaces.md +0 -33
- package/skills/dreamboard/references/index.md +0 -112
- package/skills/dreamboard/references/inputs-and-targets.md +0 -160
- package/skills/dreamboard/references/interactions.md +0 -158
- package/skills/dreamboard/references/manifest-fields.md +0 -137
- package/skills/dreamboard/references/manifest.md +0 -81
- package/skills/dreamboard/references/package-surfaces.md +0 -69
- package/skills/dreamboard/references/phases.md +0 -145
- package/skills/dreamboard/references/prompts-and-choices.md +0 -55
- package/skills/dreamboard/references/quickstart.md +0 -71
- package/skills/dreamboard/references/rule-authoring.md +0 -144
- package/skills/dreamboard/references/setup-bootstrap.md +0 -78
- package/skills/dreamboard/references/stages-and-zones.md +0 -59
- package/skills/dreamboard/references/static-views.md +0 -67
- package/skills/dreamboard/references/table-queries-and-ops.md +0 -103
- package/skills/dreamboard/references/testing-bases.md +0 -147
- package/skills/dreamboard/references/testing-generated-contracts.md +0 -142
- package/skills/dreamboard/references/testing-runtime-assertions.md +0 -124
- package/skills/dreamboard/references/testing-scenarios.md +0 -148
- package/skills/dreamboard/references/testing-ui-tests.md +0 -174
- package/skills/dreamboard/references/testing.md +0 -161
- package/skills/dreamboard/references/ui-architecture.md +0 -137
- package/skills/dreamboard/references/ui-components.md +0 -34
- package/skills/dreamboard/references/views.md +0 -72
- package/skills/dreamboard/references/workspace-layout.md +0 -136
- /package/dist/{dist-WJRJNFLI.js.map → dist-FRURQI7Q.js.map} +0 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
|
|
3
|
+
import "./host-main.css";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { createRoot } from "react-dom/client";
|
|
6
|
+
import { Wrench, X } from "lucide-react";
|
|
7
|
+
import {
|
|
8
|
+
Drawer,
|
|
9
|
+
DrawerClose,
|
|
10
|
+
DrawerContent,
|
|
11
|
+
DrawerDescription,
|
|
12
|
+
DrawerHeader,
|
|
13
|
+
DrawerTitle,
|
|
14
|
+
DrawerTrigger,
|
|
15
|
+
} from "@dreamboard/ui/components/drawer";
|
|
16
|
+
import { client } from "@dreamboard/api-client/client.gen";
|
|
17
|
+
import {
|
|
18
|
+
HostFeedbackToaster,
|
|
19
|
+
HostHistoryNavigator,
|
|
20
|
+
HostPlayerSwitcher,
|
|
21
|
+
HostSessionMetadata,
|
|
22
|
+
HostSessionToolbar,
|
|
23
|
+
PerfOverlay,
|
|
24
|
+
type HostControllablePlayer,
|
|
25
|
+
} from "@dreamboard/ui-host-runtime/components";
|
|
26
|
+
import {
|
|
27
|
+
SSEManager,
|
|
28
|
+
createUnifiedSessionStore,
|
|
29
|
+
unifiedSessionSelectors,
|
|
30
|
+
type HistoryState,
|
|
31
|
+
type HostFeedback,
|
|
32
|
+
} from "@dreamboard/ui-host-runtime/runtime";
|
|
33
|
+
import devConfig from "virtual:dreamboard-dev-config";
|
|
34
|
+
import {
|
|
35
|
+
createDevDiagnosticsLogger,
|
|
36
|
+
formatConsoleArgs,
|
|
37
|
+
resolveDevDiagnosticsLevel,
|
|
38
|
+
shouldRelayDevLog,
|
|
39
|
+
stringifyForRelay,
|
|
40
|
+
type DevLogEnvelope,
|
|
41
|
+
} from "./dev-diagnostics.js";
|
|
42
|
+
import {
|
|
43
|
+
DevHostController,
|
|
44
|
+
type DevHostRuntimeError,
|
|
45
|
+
} from "./dev-host-controller.js";
|
|
46
|
+
import { createDevHostSessionTransport } from "./dev-host-session-transport.js";
|
|
47
|
+
import {
|
|
48
|
+
SessionStorageDevHostStorage,
|
|
49
|
+
type ActiveSession,
|
|
50
|
+
} from "./dev-host-storage.js";
|
|
51
|
+
import { resolveInitialDevHostPlayerId } from "./dev-host-player-query.js";
|
|
52
|
+
|
|
53
|
+
const diagnosticsLevel = resolveDevDiagnosticsLevel(devConfig.debug);
|
|
54
|
+
const devLogger = createDevDiagnosticsLogger(diagnosticsLevel);
|
|
55
|
+
const storage = new SessionStorageDevHostStorage(window.sessionStorage);
|
|
56
|
+
const hostSessionTransport = createDevHostSessionTransport();
|
|
57
|
+
let runtimeDisposed = false;
|
|
58
|
+
|
|
59
|
+
const store = createUnifiedSessionStore({
|
|
60
|
+
createSseManager: () =>
|
|
61
|
+
new SSEManager({
|
|
62
|
+
transport: hostSessionTransport,
|
|
63
|
+
logger: {
|
|
64
|
+
log: (...args: unknown[]) => {
|
|
65
|
+
if (runtimeDisposed) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
devLogger.log(...args);
|
|
69
|
+
},
|
|
70
|
+
warn: (...args: unknown[]) => {
|
|
71
|
+
if (runtimeDisposed) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
devLogger.warn(...args);
|
|
75
|
+
},
|
|
76
|
+
error: (...args: unknown[]) => {
|
|
77
|
+
if (runtimeDisposed) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
devLogger.error(...args);
|
|
81
|
+
controller.handleSseTransportError(args);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
logger: devLogger,
|
|
86
|
+
transport: hostSessionTransport,
|
|
87
|
+
fallbackToAllSeatsWhenUserIdMissing: !devConfig.userId,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const controller = new DevHostController(
|
|
91
|
+
store,
|
|
92
|
+
storage,
|
|
93
|
+
{
|
|
94
|
+
autoStartGame: devConfig.autoStartGame,
|
|
95
|
+
compiledResultId: devConfig.compiledResultId,
|
|
96
|
+
createDevSessionSnapshot: hostSessionTransport.createDevSessionSnapshot,
|
|
97
|
+
debug: devConfig.debug,
|
|
98
|
+
fallbackSession: devConfig.initialSession,
|
|
99
|
+
gameId: devConfig.gameId,
|
|
100
|
+
initialPlayerId: resolveInitialDevHostPlayerId(window.location.search),
|
|
101
|
+
playerCount: devConfig.playerCount,
|
|
102
|
+
setupProfileId: devConfig.setupProfileId,
|
|
103
|
+
slug: devConfig.slug,
|
|
104
|
+
userId: devConfig.userId,
|
|
105
|
+
},
|
|
106
|
+
devLogger,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// The browser never sees the bearer token. All backend traffic is
|
|
110
|
+
// same-origin and the CLI's reverse-proxy middleware (`/api/*`) injects
|
|
111
|
+
// `Authorization: Bearer <fresh>` on the wire.
|
|
112
|
+
client.setConfig({ baseUrl: "" });
|
|
113
|
+
installProxyAuthErrorInterceptor();
|
|
114
|
+
|
|
115
|
+
const app = document.getElementById("app");
|
|
116
|
+
if (!(app instanceof HTMLElement)) {
|
|
117
|
+
throw new Error("Missing root app container.");
|
|
118
|
+
}
|
|
119
|
+
const root = createRoot(app);
|
|
120
|
+
|
|
121
|
+
function installProxyAuthErrorInterceptor(): void {
|
|
122
|
+
client.interceptors.response.use(async (response) => {
|
|
123
|
+
if (response.status !== 401) {
|
|
124
|
+
return response;
|
|
125
|
+
}
|
|
126
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
127
|
+
if (!contentType.includes("application/json")) {
|
|
128
|
+
return response;
|
|
129
|
+
}
|
|
130
|
+
// Clone first so downstream handlers can still read the body.
|
|
131
|
+
const clone = response.clone();
|
|
132
|
+
type AuthErrorPayload = { error?: unknown; message?: unknown };
|
|
133
|
+
let payload: AuthErrorPayload | null = null;
|
|
134
|
+
try {
|
|
135
|
+
payload = (await clone.json()) as AuthErrorPayload;
|
|
136
|
+
} catch {
|
|
137
|
+
return response;
|
|
138
|
+
}
|
|
139
|
+
if (payload?.error !== "session_invalid") {
|
|
140
|
+
return response;
|
|
141
|
+
}
|
|
142
|
+
const detail =
|
|
143
|
+
typeof payload.message === "string" && payload.message.length > 0
|
|
144
|
+
? payload.message
|
|
145
|
+
: "Stored Dreamboard session is no longer valid.";
|
|
146
|
+
controller.reportRuntimeError({
|
|
147
|
+
title: "Session expired",
|
|
148
|
+
summary: `${detail} Run \`dreamboard login\` in your terminal, then reload this page.`,
|
|
149
|
+
violations: [],
|
|
150
|
+
});
|
|
151
|
+
return response;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const restoreConsoleRelay = installConsoleRelay("host");
|
|
156
|
+
const removeWindowErrorRelay = installWindowErrorRelay("host");
|
|
157
|
+
installSseRelay();
|
|
158
|
+
window.addEventListener("message", handlePluginLogMessage);
|
|
159
|
+
window.addEventListener("pagehide", disposeHostRuntime);
|
|
160
|
+
window.addEventListener("beforeunload", disposeHostRuntime);
|
|
161
|
+
|
|
162
|
+
const unsubscribeStoreRender = store.subscribe(() => {
|
|
163
|
+
render();
|
|
164
|
+
});
|
|
165
|
+
const unsubscribeControllerRender = controller.subscribe(() => {
|
|
166
|
+
render();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
void controller.initialize();
|
|
170
|
+
|
|
171
|
+
if (import.meta.hot) {
|
|
172
|
+
import.meta.hot.accept();
|
|
173
|
+
import.meta.hot.dispose(() => {
|
|
174
|
+
disposeHostRuntime();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function render(): void {
|
|
179
|
+
if (runtimeDisposed) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const state = store.getState();
|
|
184
|
+
const controllerState = controller.getSnapshot();
|
|
185
|
+
const session = state.getPluginSnapshot().session;
|
|
186
|
+
const seats = unifiedSessionSelectors.seats(state);
|
|
187
|
+
const controllablePlayers: HostControllablePlayer[] =
|
|
188
|
+
session.controllablePlayerIds.map((playerId) => {
|
|
189
|
+
const seat = seats.find((entry) => entry.playerId === playerId);
|
|
190
|
+
return {
|
|
191
|
+
playerId,
|
|
192
|
+
displayName: seat?.displayName || playerId,
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
const isHost = unifiedSessionSelectors.isSessionHost(state);
|
|
196
|
+
const phase = unifiedSessionSelectors.sessionType(state);
|
|
197
|
+
|
|
198
|
+
root.render(
|
|
199
|
+
<DevHostApp
|
|
200
|
+
session={controllerState.session}
|
|
201
|
+
phase={phase}
|
|
202
|
+
bootstrapStatus={unifiedSessionSelectors.bootstrapStatus(state)}
|
|
203
|
+
isConnected={unifiedSessionSelectors.isConnected(state)}
|
|
204
|
+
connectionError={unifiedSessionSelectors.connectionError(state)}
|
|
205
|
+
runtimeError={controllerState.runtimeError}
|
|
206
|
+
syncId={unifiedSessionSelectors.syncId(state)}
|
|
207
|
+
pluginReady={controllerState.pluginReady}
|
|
208
|
+
controllablePlayers={controllablePlayers}
|
|
209
|
+
controllingPlayerId={
|
|
210
|
+
unifiedSessionSelectors.currentPlayerId(state) ??
|
|
211
|
+
session.controllingPlayerId
|
|
212
|
+
}
|
|
213
|
+
canStart={phase === "lobby" && unifiedSessionSelectors.canStart(state)}
|
|
214
|
+
isHost={isHost}
|
|
215
|
+
history={unifiedSessionSelectors.history(state)}
|
|
216
|
+
hostFeedback={unifiedSessionSelectors.hostFeedback(state)}
|
|
217
|
+
iframeSrc={controllerState.iframeSrc}
|
|
218
|
+
seedValue={controllerState.seedValue}
|
|
219
|
+
isCreatingSession={controllerState.isCreatingSession}
|
|
220
|
+
onSeedChange={(value) => controller.setSeedValue(value)}
|
|
221
|
+
onCreateSession={() => void controller.createNewSession()}
|
|
222
|
+
onStartGame={() => void controller.startGameFromSidebar()}
|
|
223
|
+
onSwitchPlayer={(playerId) => controller.switchPlayer(playerId)}
|
|
224
|
+
onRestoreHistory={(entryId) => controller.restoreHistoryEntry(entryId)}
|
|
225
|
+
onDismissHostFeedback={(feedbackId) =>
|
|
226
|
+
store.getState().dismissHostFeedback(feedbackId)
|
|
227
|
+
}
|
|
228
|
+
onDismissRuntimeError={() => controller.dismissRuntimeError()}
|
|
229
|
+
onIframeReady={(element) => {
|
|
230
|
+
controller.setIframe(element);
|
|
231
|
+
}}
|
|
232
|
+
onIframeLoad={() => {
|
|
233
|
+
controller.onIframeLoad();
|
|
234
|
+
}}
|
|
235
|
+
/>,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
type DevHostAppProps = {
|
|
240
|
+
session: ActiveSession;
|
|
241
|
+
phase: string;
|
|
242
|
+
bootstrapStatus: "loading" | "lobby" | "renderable" | "error";
|
|
243
|
+
isConnected: boolean;
|
|
244
|
+
connectionError: string | null;
|
|
245
|
+
runtimeError: DevHostRuntimeError | null;
|
|
246
|
+
syncId: number;
|
|
247
|
+
pluginReady: boolean;
|
|
248
|
+
controllablePlayers: HostControllablePlayer[];
|
|
249
|
+
controllingPlayerId: string | null;
|
|
250
|
+
canStart: boolean;
|
|
251
|
+
isHost: boolean;
|
|
252
|
+
history: HistoryState | null;
|
|
253
|
+
hostFeedback: HostFeedback[];
|
|
254
|
+
iframeSrc: string;
|
|
255
|
+
seedValue: string;
|
|
256
|
+
isCreatingSession: boolean;
|
|
257
|
+
onSeedChange: (value: string) => void;
|
|
258
|
+
onCreateSession: () => void;
|
|
259
|
+
onStartGame: () => void;
|
|
260
|
+
onSwitchPlayer: (playerId: string) => void;
|
|
261
|
+
onRestoreHistory: (entryId: string) => Promise<void>;
|
|
262
|
+
onDismissHostFeedback: (feedbackId: string) => void;
|
|
263
|
+
onDismissRuntimeError: () => void;
|
|
264
|
+
onIframeReady: (element: HTMLIFrameElement | null) => void;
|
|
265
|
+
onIframeLoad: () => void;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
function DevHostApp({
|
|
269
|
+
session,
|
|
270
|
+
phase,
|
|
271
|
+
bootstrapStatus,
|
|
272
|
+
isConnected,
|
|
273
|
+
connectionError,
|
|
274
|
+
runtimeError,
|
|
275
|
+
syncId,
|
|
276
|
+
pluginReady,
|
|
277
|
+
controllablePlayers,
|
|
278
|
+
controllingPlayerId,
|
|
279
|
+
canStart,
|
|
280
|
+
isHost,
|
|
281
|
+
history,
|
|
282
|
+
hostFeedback,
|
|
283
|
+
iframeSrc,
|
|
284
|
+
seedValue,
|
|
285
|
+
isCreatingSession,
|
|
286
|
+
onSeedChange,
|
|
287
|
+
onCreateSession,
|
|
288
|
+
onStartGame,
|
|
289
|
+
onSwitchPlayer,
|
|
290
|
+
onRestoreHistory,
|
|
291
|
+
onDismissHostFeedback,
|
|
292
|
+
onDismissRuntimeError,
|
|
293
|
+
onIframeReady,
|
|
294
|
+
onIframeLoad,
|
|
295
|
+
}: DevHostAppProps) {
|
|
296
|
+
const [isSidebarOpen, setIsSidebarOpen] = useState(() =>
|
|
297
|
+
storage.loadSidebarOpen(),
|
|
298
|
+
);
|
|
299
|
+
const needsBootstrap = phase !== "error" && bootstrapStatus === "loading";
|
|
300
|
+
|
|
301
|
+
const handleToggleSidebar = (open: boolean) => {
|
|
302
|
+
setIsSidebarOpen(open);
|
|
303
|
+
storage.persistSidebarOpen(open);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<div className="relative flex h-full w-full overflow-hidden bg-transparent font-sans text-foreground">
|
|
308
|
+
<HostFeedbackToaster
|
|
309
|
+
feedback={hostFeedback}
|
|
310
|
+
onDismiss={onDismissHostFeedback}
|
|
311
|
+
/>
|
|
312
|
+
<main className="absolute inset-0 z-0 flex flex-col bg-transparent">
|
|
313
|
+
<iframe
|
|
314
|
+
ref={onIframeReady}
|
|
315
|
+
src={iframeSrc}
|
|
316
|
+
referrerPolicy="no-referrer"
|
|
317
|
+
title="Dreamboard UI Plugin"
|
|
318
|
+
className="h-full w-full flex-1 border-0 bg-background"
|
|
319
|
+
onLoad={onIframeLoad}
|
|
320
|
+
/>
|
|
321
|
+
{needsBootstrap ? (
|
|
322
|
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-[#f7f1da]/92 px-6 text-center backdrop-blur-[2px]">
|
|
323
|
+
<div className="max-w-md rounded-lg border border-border bg-white px-5 py-4 shadow-md">
|
|
324
|
+
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
|
325
|
+
Dev Host
|
|
326
|
+
</p>
|
|
327
|
+
<h2 className="mt-2 text-xl font-semibold text-foreground">
|
|
328
|
+
Waiting for session bootstrap
|
|
329
|
+
</h2>
|
|
330
|
+
<p className="mt-3 text-sm font-medium text-foreground">
|
|
331
|
+
Attaching to{" "}
|
|
332
|
+
<span className="font-semibold">{session.shortCode}</span> (
|
|
333
|
+
{session.sessionId}).
|
|
334
|
+
</p>
|
|
335
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
336
|
+
The host will create a fresh session automatically if the
|
|
337
|
+
current one cannot finish bootstrapping.
|
|
338
|
+
</p>
|
|
339
|
+
{connectionError ? (
|
|
340
|
+
<p className="mt-3 rounded-md border border-border bg-[#ffe1d6] px-3 py-2 text-sm font-semibold text-foreground">
|
|
341
|
+
{connectionError}
|
|
342
|
+
</p>
|
|
343
|
+
) : null}
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
) : null}
|
|
347
|
+
{runtimeError ? (
|
|
348
|
+
<RuntimeErrorOverlay
|
|
349
|
+
error={runtimeError}
|
|
350
|
+
onDismiss={onDismissRuntimeError}
|
|
351
|
+
/>
|
|
352
|
+
) : null}
|
|
353
|
+
</main>
|
|
354
|
+
|
|
355
|
+
<Drawer
|
|
356
|
+
open={isSidebarOpen}
|
|
357
|
+
onOpenChange={handleToggleSidebar}
|
|
358
|
+
direction="left"
|
|
359
|
+
>
|
|
360
|
+
{!isSidebarOpen ? (
|
|
361
|
+
<DrawerTrigger asChild>
|
|
362
|
+
<button
|
|
363
|
+
type="button"
|
|
364
|
+
className="absolute left-4 top-4 z-50 flex items-center gap-2 rounded-lg border border-border bg-[#fff9c4] px-3 py-1.5 text-sm font-semibold text-foreground shadow-md transition-colors hover:bg-[#ffe566]"
|
|
365
|
+
>
|
|
366
|
+
<Wrench className="h-4 w-4" />
|
|
367
|
+
Dev Tools
|
|
368
|
+
</button>
|
|
369
|
+
</DrawerTrigger>
|
|
370
|
+
) : null}
|
|
371
|
+
|
|
372
|
+
<DrawerContent className="dev-drawer-content h-full w-[380px] max-w-[calc(100vw-1rem)] border-r border-border p-0 shadow-xl backdrop-blur-md">
|
|
373
|
+
<div className="dev-drawer-surface flex h-full flex-col overflow-hidden">
|
|
374
|
+
<DrawerHeader className="dev-drawer-header relative overflow-hidden border-b border-border px-5 pb-5 pt-4">
|
|
375
|
+
<DrawerDescription className="sr-only">
|
|
376
|
+
Developer controls for the local Dreamboard host session.
|
|
377
|
+
</DrawerDescription>
|
|
378
|
+
<div className="relative flex items-start justify-between gap-4">
|
|
379
|
+
<div>
|
|
380
|
+
<p className="dev-drawer-label mb-2 inline-block rounded-md px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
|
381
|
+
Dreamboard Dev Host
|
|
382
|
+
</p>
|
|
383
|
+
<DrawerTitle className="truncate pr-2 text-xl font-semibold text-foreground">
|
|
384
|
+
{devConfig.slug}
|
|
385
|
+
</DrawerTitle>
|
|
386
|
+
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
387
|
+
<div className="dev-drawer-session-pill inline-flex max-w-full items-center gap-2 rounded-md border border-border px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em]">
|
|
388
|
+
<span className="dev-drawer-session-dot h-2.5 w-2.5 rounded-full border border-border" />
|
|
389
|
+
<span className="truncate">{session.shortCode}</span>
|
|
390
|
+
</div>
|
|
391
|
+
<StatusPill
|
|
392
|
+
label={describePhase(phase, isConnected, pluginReady)}
|
|
393
|
+
/>
|
|
394
|
+
<div className="dev-drawer-note inline-flex items-center rounded-md border border-border/40 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
395
|
+
Sync {syncId}
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
<DrawerClose asChild>
|
|
400
|
+
<button
|
|
401
|
+
type="button"
|
|
402
|
+
className="dev-drawer-close relative z-10 rounded-md border border-border p-1.5 transition-colors hover:bg-accent"
|
|
403
|
+
title="Hide Dev Tools"
|
|
404
|
+
>
|
|
405
|
+
<X className="h-4 w-4" />
|
|
406
|
+
</button>
|
|
407
|
+
</DrawerClose>
|
|
408
|
+
</div>
|
|
409
|
+
</DrawerHeader>
|
|
410
|
+
|
|
411
|
+
<div className="flex-1 overflow-y-auto px-4 pb-5 pt-4">
|
|
412
|
+
<div className="space-y-4">
|
|
413
|
+
<section className="dev-drawer-card relative overflow-hidden rounded-lg border border-border p-4 shadow-sm">
|
|
414
|
+
<p className="dev-drawer-card-title mb-3 border-b border-border/40 pb-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
|
415
|
+
Session
|
|
416
|
+
</p>
|
|
417
|
+
<HostSessionMetadata
|
|
418
|
+
gameId={session.gameId}
|
|
419
|
+
sessionId={session.sessionId}
|
|
420
|
+
shortCode={session.shortCode}
|
|
421
|
+
/>
|
|
422
|
+
<div className="dev-drawer-note mt-4 rounded-md border border-border/40 px-3 py-2 text-sm">
|
|
423
|
+
Backend: {devConfig.apiBaseUrl}
|
|
424
|
+
</div>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
427
|
+
<section className="dev-drawer-controls-card relative overflow-hidden rounded-lg border border-border p-4 shadow-sm">
|
|
428
|
+
<p className="dev-drawer-card-title mb-4 border-b border-border/40 pb-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
|
429
|
+
Controls
|
|
430
|
+
</p>
|
|
431
|
+
<div className="space-y-3">
|
|
432
|
+
<label
|
|
433
|
+
className="block text-sm font-semibold text-foreground"
|
|
434
|
+
htmlFor="seed-input"
|
|
435
|
+
>
|
|
436
|
+
Seed
|
|
437
|
+
</label>
|
|
438
|
+
<input
|
|
439
|
+
id="seed-input"
|
|
440
|
+
type="number"
|
|
441
|
+
inputMode="numeric"
|
|
442
|
+
className="dev-drawer-input w-full rounded-md border border-border px-3 py-2 text-sm font-medium font-sans outline-none transition focus:ring-2 focus:ring-primary/30"
|
|
443
|
+
value={seedValue}
|
|
444
|
+
onChange={(event) => onSeedChange(event.target.value)}
|
|
445
|
+
/>
|
|
446
|
+
<button
|
|
447
|
+
type="button"
|
|
448
|
+
className="dev-drawer-primary-button w-full rounded-md border border-border px-4 py-2 text-sm font-semibold shadow-sm transition-colors hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
|
449
|
+
disabled={isCreatingSession}
|
|
450
|
+
onClick={onCreateSession}
|
|
451
|
+
>
|
|
452
|
+
{isCreatingSession ? "Creating..." : "New Session"}
|
|
453
|
+
</button>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
456
|
+
<div className="mt-4 space-y-3">
|
|
457
|
+
{canStart ? (
|
|
458
|
+
<button
|
|
459
|
+
type="button"
|
|
460
|
+
className="dev-drawer-danger-button w-full rounded-md border border-border px-4 py-2 text-sm font-semibold shadow-sm transition-colors"
|
|
461
|
+
onClick={onStartGame}
|
|
462
|
+
>
|
|
463
|
+
Start Game
|
|
464
|
+
</button>
|
|
465
|
+
) : null}
|
|
466
|
+
<div className="dev-drawer-toolbar-shell rounded-md border border-dashed border-border/50 p-3">
|
|
467
|
+
<HostSessionToolbar className="justify-center flex-wrap gap-2">
|
|
468
|
+
<HostPlayerSwitcher
|
|
469
|
+
controllablePlayers={controllablePlayers}
|
|
470
|
+
controllingPlayerId={controllingPlayerId}
|
|
471
|
+
onSwitchPlayer={onSwitchPlayer}
|
|
472
|
+
/>
|
|
473
|
+
<HostHistoryNavigator
|
|
474
|
+
isHost={isHost}
|
|
475
|
+
history={history}
|
|
476
|
+
onRestoreHistory={onRestoreHistory}
|
|
477
|
+
/>
|
|
478
|
+
</HostSessionToolbar>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
</section>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
</DrawerContent>
|
|
486
|
+
</Drawer>
|
|
487
|
+
<PerfOverlay />
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function StatusPill({ label }: { label: string }) {
|
|
493
|
+
return (
|
|
494
|
+
<div className="dev-drawer-note inline-flex items-center rounded-md border border-border/40 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
495
|
+
{label}
|
|
496
|
+
</div>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Full-viewport overlay that surfaces runtime failures (e.g. reducer
|
|
502
|
+
* `initialize` rejections, session-start 500s) to the game author instead of
|
|
503
|
+
* burying them in the backend log. Each violation is rendered as its own
|
|
504
|
+
* block so the JS stack trace — which the backend already parses into a
|
|
505
|
+
* separate violation entry — stays readable with file/line detail intact.
|
|
506
|
+
*/
|
|
507
|
+
function RuntimeErrorOverlay({
|
|
508
|
+
error,
|
|
509
|
+
onDismiss,
|
|
510
|
+
}: {
|
|
511
|
+
error: DevHostRuntimeError;
|
|
512
|
+
onDismiss: () => void;
|
|
513
|
+
}) {
|
|
514
|
+
const headlineViolation =
|
|
515
|
+
error.violations.length > 0 ? error.violations[0] : null;
|
|
516
|
+
const stackViolations = error.violations.slice(1);
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
<div className="absolute inset-0 z-40 flex items-center justify-center bg-[#2d2d2d]/70 px-6 py-8 backdrop-blur-[2px]">
|
|
520
|
+
<div className="relative max-h-[85vh] w-full max-w-2xl overflow-hidden rounded-lg border border-border bg-[#fff7e5] shadow-xl">
|
|
521
|
+
<div className="flex items-start justify-between gap-4 border-b border-border bg-[#ffd3d3] px-5 py-4">
|
|
522
|
+
<div>
|
|
523
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-foreground/70">
|
|
524
|
+
Runtime error
|
|
525
|
+
</p>
|
|
526
|
+
<h2 className="mt-1 text-xl font-semibold text-foreground">
|
|
527
|
+
{error.title}
|
|
528
|
+
</h2>
|
|
529
|
+
</div>
|
|
530
|
+
<button
|
|
531
|
+
type="button"
|
|
532
|
+
onClick={onDismiss}
|
|
533
|
+
className="shrink-0 rounded-md border border-border bg-white p-1.5 transition-colors hover:bg-accent"
|
|
534
|
+
title="Dismiss"
|
|
535
|
+
>
|
|
536
|
+
<X className="h-4 w-4" />
|
|
537
|
+
</button>
|
|
538
|
+
</div>
|
|
539
|
+
<div className="max-h-[calc(85vh-88px)] overflow-y-auto px-5 py-4">
|
|
540
|
+
<p className="text-sm font-medium text-foreground">{error.summary}</p>
|
|
541
|
+
{headlineViolation ? (
|
|
542
|
+
<div className="mt-4 rounded-md border border-border bg-white px-3 py-2">
|
|
543
|
+
<p className="text-[10px] font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
|
544
|
+
{headlineViolation.code ?? "message"}
|
|
545
|
+
{headlineViolation.field ? ` · ${headlineViolation.field}` : ""}
|
|
546
|
+
</p>
|
|
547
|
+
<p className="mt-1 whitespace-pre-wrap break-words text-sm font-semibold text-foreground">
|
|
548
|
+
{headlineViolation.message}
|
|
549
|
+
</p>
|
|
550
|
+
</div>
|
|
551
|
+
) : null}
|
|
552
|
+
{stackViolations.length > 0 ? (
|
|
553
|
+
<details className="mt-4" open>
|
|
554
|
+
<summary className="cursor-pointer text-[11px] font-semibold uppercase tracking-[0.2em] text-foreground/70">
|
|
555
|
+
Stack trace
|
|
556
|
+
</summary>
|
|
557
|
+
<pre className="mt-2 max-h-64 overflow-auto rounded-md border border-border bg-[#1f1f1f] p-3 text-[11px] leading-relaxed text-[#f8f8f2]">
|
|
558
|
+
{stackViolations
|
|
559
|
+
.map((violation) => violation.message)
|
|
560
|
+
.join("\n")}
|
|
561
|
+
</pre>
|
|
562
|
+
</details>
|
|
563
|
+
) : null}
|
|
564
|
+
{error.correlationId ? (
|
|
565
|
+
<p className="mt-4 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
|
566
|
+
Request ID · {error.correlationId}
|
|
567
|
+
</p>
|
|
568
|
+
) : null}
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
</div>
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function installSseRelay(): void {
|
|
576
|
+
let lastLoggedEventId = 0;
|
|
577
|
+
store.subscribe((state) => {
|
|
578
|
+
const nextEntries = unifiedSessionSelectors
|
|
579
|
+
.sseEvents(state)
|
|
580
|
+
.filter((entry) => entry.id > lastLoggedEventId);
|
|
581
|
+
if (nextEntries.length === 0) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
for (const entry of nextEntries) {
|
|
586
|
+
lastLoggedEventId = entry.id;
|
|
587
|
+
relayBrowserLog({
|
|
588
|
+
source: "sse",
|
|
589
|
+
level: "info",
|
|
590
|
+
message: devConfig.debug
|
|
591
|
+
? `${entry.eventType} ${stringifyForRelay(entry.data)}`
|
|
592
|
+
: `${entry.eventType} toUser=${getMessageRecipient(entry.data) ?? "-"}`,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function getMessageRecipient(message: unknown): unknown {
|
|
599
|
+
return message && typeof message === "object" && "toUser" in message
|
|
600
|
+
? (message as { toUser?: unknown }).toUser
|
|
601
|
+
: null;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function installConsoleRelay(source: "host"): () => void {
|
|
605
|
+
const original = {
|
|
606
|
+
log: console.log.bind(console),
|
|
607
|
+
warn: console.warn.bind(console),
|
|
608
|
+
error: console.error.bind(console),
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
console.log = (...args: unknown[]) => {
|
|
612
|
+
original.log(...args);
|
|
613
|
+
relayBrowserLog({
|
|
614
|
+
source,
|
|
615
|
+
level: "log",
|
|
616
|
+
message: formatConsoleArgs(args),
|
|
617
|
+
});
|
|
618
|
+
};
|
|
619
|
+
console.warn = (...args: unknown[]) => {
|
|
620
|
+
original.warn(...args);
|
|
621
|
+
relayBrowserLog({
|
|
622
|
+
source,
|
|
623
|
+
level: "warn",
|
|
624
|
+
message: formatConsoleArgs(args),
|
|
625
|
+
});
|
|
626
|
+
};
|
|
627
|
+
console.error = (...args: unknown[]) => {
|
|
628
|
+
original.error(...args);
|
|
629
|
+
relayBrowserLog({
|
|
630
|
+
source,
|
|
631
|
+
level: "error",
|
|
632
|
+
message: formatConsoleArgs(args),
|
|
633
|
+
});
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
return () => {
|
|
637
|
+
console.log = original.log;
|
|
638
|
+
console.warn = original.warn;
|
|
639
|
+
console.error = original.error;
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function installWindowErrorRelay(source: "host"): () => void {
|
|
644
|
+
const onError = (event: ErrorEvent) => {
|
|
645
|
+
if (runtimeDisposed || shouldIgnoreBrowserError(event.error)) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
relayBrowserLog({
|
|
649
|
+
source,
|
|
650
|
+
level: "error",
|
|
651
|
+
message: `window.error ${event.message}`,
|
|
652
|
+
});
|
|
653
|
+
};
|
|
654
|
+
const onUnhandledRejection = (event: PromiseRejectionEvent) => {
|
|
655
|
+
if (runtimeDisposed || shouldIgnoreBrowserError(event.reason)) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
relayBrowserLog({
|
|
659
|
+
source,
|
|
660
|
+
level: "error",
|
|
661
|
+
message: `unhandledrejection ${stringifyForRelay(event.reason)}`,
|
|
662
|
+
});
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
window.addEventListener("error", onError);
|
|
666
|
+
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
667
|
+
|
|
668
|
+
return () => {
|
|
669
|
+
window.removeEventListener("error", onError);
|
|
670
|
+
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function handlePluginLogMessage(event: MessageEvent): void {
|
|
675
|
+
if (!controller.matchesPluginWindow(event.source)) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const payload = event.data as Partial<DevLogEnvelope> & { type?: string };
|
|
680
|
+
if (
|
|
681
|
+
!payload ||
|
|
682
|
+
typeof payload !== "object" ||
|
|
683
|
+
payload.type !== "dreamboard-dev-console"
|
|
684
|
+
) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
relayBrowserLog({
|
|
689
|
+
source: "plugin",
|
|
690
|
+
level:
|
|
691
|
+
payload.level === "warn" ||
|
|
692
|
+
payload.level === "error" ||
|
|
693
|
+
payload.level === "log"
|
|
694
|
+
? payload.level
|
|
695
|
+
: "log",
|
|
696
|
+
message:
|
|
697
|
+
typeof payload.message === "string"
|
|
698
|
+
? payload.message
|
|
699
|
+
: stringifyForRelay(payload.message),
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function relayBrowserLog(payload: DevLogEnvelope): void {
|
|
704
|
+
if (runtimeDisposed || !shouldRelayDevLog(diagnosticsLevel, payload)) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
void fetch("/__dreamboard_dev/log", {
|
|
709
|
+
method: "POST",
|
|
710
|
+
headers: { "content-type": "application/json" },
|
|
711
|
+
body: JSON.stringify(payload),
|
|
712
|
+
keepalive: true,
|
|
713
|
+
}).catch(() => {
|
|
714
|
+
// Ignore log relay failures to avoid recursive console noise.
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function disposeHostRuntime(): void {
|
|
719
|
+
if (runtimeDisposed) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
runtimeDisposed = true;
|
|
724
|
+
removeWindowErrorRelay();
|
|
725
|
+
restoreConsoleRelay();
|
|
726
|
+
window.removeEventListener("message", handlePluginLogMessage);
|
|
727
|
+
window.removeEventListener("pagehide", disposeHostRuntime);
|
|
728
|
+
window.removeEventListener("beforeunload", disposeHostRuntime);
|
|
729
|
+
unsubscribeStoreRender();
|
|
730
|
+
unsubscribeControllerRender();
|
|
731
|
+
controller.dispose();
|
|
732
|
+
root.unmount();
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function shouldIgnoreBrowserError(value: unknown): boolean {
|
|
736
|
+
return value instanceof Error && value.name === "AbortError";
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function describePhase(
|
|
740
|
+
phase: string,
|
|
741
|
+
isConnected: boolean,
|
|
742
|
+
isPluginReady: boolean,
|
|
743
|
+
): string {
|
|
744
|
+
if (phase === "error") {
|
|
745
|
+
return "Error";
|
|
746
|
+
}
|
|
747
|
+
if (!isConnected) {
|
|
748
|
+
return "Connecting SSE";
|
|
749
|
+
}
|
|
750
|
+
if (!isPluginReady) {
|
|
751
|
+
return "Loading iframe";
|
|
752
|
+
}
|
|
753
|
+
return phase === "gameplay" ? "Live gameplay" : phase;
|
|
754
|
+
}
|