@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.
- package/README.md +27 -108
- package/dist/agent-verifier/agent-workspace-verifier.mjs +1988 -57
- package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
- package/dist/agent-verifier/{chunk-XQXDOBYB.mjs → chunk-4I2WWAPK.mjs} +27 -10
- package/dist/agent-verifier/chunk-4I2WWAPK.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-O4YCPU7C.mjs → chunk-BWBN2TDJ.mjs} +539 -641
- package/dist/agent-verifier/chunk-BWBN2TDJ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-TAEQKBJB.mjs → chunk-GWRZRWCF.mjs} +1 -1
- package/dist/agent-verifier/chunk-GWRZRWCF.mjs.map +1 -0
- package/dist/agent-verifier/chunk-H6XDQJ3N.mjs +11 -0
- package/dist/agent-verifier/chunk-HUBV22JQ.mjs +89 -0
- package/dist/agent-verifier/chunk-HUBV22JQ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-VS573ERH.mjs → chunk-JZTH3EMV.mjs} +2 -2
- package/dist/agent-verifier/{chunk-XGWCY624.mjs → chunk-KDAQ4CZY.mjs} +34 -27
- package/dist/agent-verifier/chunk-KDAQ4CZY.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-IAYRNVUC.mjs → chunk-LMW66VBH.mjs} +2 -13
- package/dist/agent-verifier/{chunk-IAYRNVUC.mjs.map → chunk-LMW66VBH.mjs.map} +1 -1
- package/dist/agent-verifier/{chunk-776W3UGV.mjs → chunk-M6YNQZCC.mjs} +4 -13
- package/dist/agent-verifier/chunk-M6YNQZCC.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-H76MT5UR.mjs → chunk-M7UVBANQ.mjs} +2 -1
- package/dist/agent-verifier/chunk-M7UVBANQ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-SH5JKYOB.mjs → chunk-MIRGCMUC.mjs} +112 -26
- package/dist/agent-verifier/chunk-MIRGCMUC.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-NAK77WXW.mjs → chunk-MYMVXTZT.mjs} +4 -5
- package/dist/agent-verifier/chunk-MYMVXTZT.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-7WWGFAAU.mjs → chunk-NBRUEJUK.mjs} +215 -223
- package/dist/agent-verifier/chunk-NBRUEJUK.mjs.map +1 -0
- package/dist/agent-verifier/chunk-OJFZVGEL.mjs +492 -0
- package/dist/agent-verifier/chunk-OJFZVGEL.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-LUZ7KE6H.mjs → chunk-QD4SQNUP.mjs} +4 -8
- package/dist/agent-verifier/{chunk-LUZ7KE6H.mjs.map → chunk-QD4SQNUP.mjs.map} +1 -1
- package/dist/agent-verifier/chunk-TTB7AIHZ.mjs +214 -0
- package/dist/agent-verifier/chunk-TTB7AIHZ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-F2DIOJJZ.mjs → chunk-XCQQIPCO.mjs} +5 -46
- package/dist/agent-verifier/chunk-XCQQIPCO.mjs.map +1 -0
- package/dist/agent-verifier/{global-config-Y2NTSK4R.mjs → global-config-2NUESNEQ.mjs} +6 -6
- package/dist/agent-verifier/{keychain-backend-SPQWGKZN.mjs → keychain-backend-FF4I6ODB.mjs} +12 -7
- package/dist/agent-verifier/keychain-backend-FF4I6ODB.mjs.map +1 -0
- package/dist/agent-verifier/{local-files-JFOQQZDL.mjs → local-files-OF4QFISU.mjs} +10 -10
- package/dist/agent-verifier/{chunk-UIOLGH4A.mjs → local-typecheck-DHVLM37Z.mjs} +4 -4
- package/dist/agent-verifier/local-typecheck-DHVLM37Z.mjs.map +1 -0
- package/dist/agent-verifier/{materialize-workspace-ZAVGQQSF.mjs → materialize-workspace-MAGKDMK5.mjs} +23 -22
- package/dist/agent-verifier/materialize-workspace-MAGKDMK5.mjs.map +1 -0
- package/dist/agent-verifier/{project-state-K576C2TE.mjs → project-state-XKUSCFSV.mjs} +2 -2
- package/dist/agent-verifier/{prompt-MJRJMOGQ.mjs → prompt-VKHMCQT6.mjs} +2 -2
- package/dist/agent-verifier/{chunk-A64ZZUZV.mjs → reducer-bundle-preflight-GLUJKTWU.mjs} +76 -25
- package/dist/agent-verifier/reducer-bundle-preflight-GLUJKTWU.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-JGT4P4UD.mjs → reducer-contract-preflight-WVQQPW5F.mjs} +7 -6
- package/dist/agent-verifier/reducer-contract-preflight-WVQQPW5F.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-E7SSWJXJ.mjs → reducer-native-test-harness-UFMSNNDY.mjs} +463 -686
- package/dist/agent-verifier/reducer-native-test-harness-UFMSNNDY.mjs.map +1 -0
- package/dist/agent-verifier/static-scaffold-CLRRWXON.mjs +24 -0
- package/dist/agent-verifier/workspace-codegen-SPPVHURX.mjs +10 -0
- package/dist/agent-verifier/{workspace-dependencies-NOOQBK6I.mjs → workspace-dependencies-5HEEKZFP.mjs} +6 -4
- package/dist/authoring-compatibility-internal.js +12 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-6NYVJYN4.js +313 -0
- package/dist/chunk-6NYVJYN4.js.map +1 -0
- package/dist/chunk-EQNBQVIW.js +204 -0
- package/dist/chunk-EQNBQVIW.js.map +1 -0
- package/dist/chunk-X244CUU4.js +3815 -0
- package/dist/chunk-X244CUU4.js.map +1 -0
- package/dist/{chunk-TAQKH67O.js → chunk-YNJVKC2T.js} +2587 -7278
- package/dist/chunk-YNJVKC2T.js.map +1 -0
- package/dist/{global-config-S4ZIPECE.js → global-config-RBMW7IVA.js} +4 -3
- package/dist/index.js +3099 -6187
- package/dist/index.js.map +1 -1
- package/dist/internal.js +36 -10
- package/dist/internal.js.map +1 -1
- package/dist/{keychain-backend-HDF4TZDL.js → keychain-backend-FSNTNTZE.js} +12 -7
- package/dist/keychain-backend-FSNTNTZE.js.map +1 -0
- package/dist/{prompt-NDV3AE5L.js → prompt-GMZABCJC.js} +2 -2
- package/package.json +9 -19
- package/release/authoring-release-set.json +38 -0
- package/skills/dreamboard/SKILL.md +30 -28
- package/skills/dreamboard/references/building-your-first-game.md +15 -15
- package/skills/dreamboard/references/cli.md +46 -47
- package/skills/dreamboard/references/manifest-authoring.md +11 -3
- package/skills/dreamboard/references/quickstart.md +16 -13
- package/skills/dreamboard/references/testing.md +6 -13
- package/dist/agent-verifier/chunk-3UKQVWLV.mjs +0 -1744
- package/dist/agent-verifier/chunk-3UKQVWLV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-776W3UGV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-7WWGFAAU.mjs.map +0 -1
- package/dist/agent-verifier/chunk-A64ZZUZV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-E7SSWJXJ.mjs.map +0 -1
- package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +0 -1
- package/dist/agent-verifier/chunk-G42BGGG2.mjs +0 -70
- package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +0 -1
- package/dist/agent-verifier/chunk-H76MT5UR.mjs.map +0 -1
- package/dist/agent-verifier/chunk-HGMUAL33.mjs +0 -39
- package/dist/agent-verifier/chunk-HGMUAL33.mjs.map +0 -1
- package/dist/agent-verifier/chunk-JGT4P4UD.mjs.map +0 -1
- package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +0 -1
- package/dist/agent-verifier/chunk-O4YCPU7C.mjs.map +0 -1
- package/dist/agent-verifier/chunk-S34FRJHS.mjs +0 -222
- package/dist/agent-verifier/chunk-S34FRJHS.mjs.map +0 -1
- package/dist/agent-verifier/chunk-SH5JKYOB.mjs.map +0 -1
- package/dist/agent-verifier/chunk-SKI2ESE5.mjs +0 -44
- package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +0 -1
- package/dist/agent-verifier/chunk-UIOLGH4A.mjs.map +0 -1
- package/dist/agent-verifier/chunk-UIZNWRM6.mjs +0 -2432
- package/dist/agent-verifier/chunk-UIZNWRM6.mjs.map +0 -1
- package/dist/agent-verifier/chunk-W3N3QJ4V.mjs +0 -624
- package/dist/agent-verifier/chunk-W3N3QJ4V.mjs.map +0 -1
- package/dist/agent-verifier/chunk-XGWCY624.mjs.map +0 -1
- package/dist/agent-verifier/chunk-XQXDOBYB.mjs.map +0 -1
- package/dist/agent-verifier/compile-TEQVA46V.mjs +0 -312
- package/dist/agent-verifier/compile-TEQVA46V.mjs.map +0 -1
- package/dist/agent-verifier/keychain-backend-SPQWGKZN.mjs.map +0 -1
- package/dist/agent-verifier/local-typecheck-XVGWI75X.mjs +0 -10
- package/dist/agent-verifier/materialize-workspace-ZAVGQQSF.mjs.map +0 -1
- package/dist/agent-verifier/reducer-bundle-preflight-LXNJUBKL.mjs +0 -20
- package/dist/agent-verifier/reducer-contract-preflight-TUMQ43JV.mjs +0 -11
- package/dist/agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs +0 -50
- package/dist/agent-verifier/static-scaffold-R7SVDRQI.mjs +0 -27
- package/dist/agent-verifier/sync-THAI546U.mjs +0 -588
- package/dist/agent-verifier/sync-THAI546U.mjs.map +0 -1
- package/dist/agent-verifier/test-AFAQFKOB.mjs +0 -353
- package/dist/agent-verifier/test-AFAQFKOB.mjs.map +0 -1
- package/dist/agent-verifier/workspace-codegen-2ZMQRIKJ.mjs +0 -10
- package/dist/agent-verifier/workspace-dependencies-NOOQBK6I.mjs.map +0 -1
- package/dist/chunk-N7XPNNUI.js +0 -432
- package/dist/chunk-N7XPNNUI.js.map +0 -1
- package/dist/chunk-SEGVTWSK.js +0 -44
- package/dist/chunk-SEGVTWSK.js.map +0 -1
- package/dist/chunk-TAQKH67O.js.map +0 -1
- package/dist/dev-host/components/drawer.tsx +0 -132
- package/dist/dev-host/components/input.tsx +0 -21
- package/dist/dev-host/dev-api-proxy-plugin.ts +0 -328
- package/dist/dev-host/dev-author-dom-warnings.ts +0 -100
- package/dist/dev-host/dev-diagnostics.ts +0 -62
- package/dist/dev-host/dev-fallback-stylesheet.ts +0 -53
- package/dist/dev-host/dev-hmr-guard-plugin.ts +0 -47
- package/dist/dev-host/dev-host-controller.ts +0 -674
- package/dist/dev-host/dev-host-player-query.ts +0 -17
- package/dist/dev-host/dev-host-session-transport.ts +0 -52
- package/dist/dev-host/dev-host-storage.ts +0 -56
- package/dist/dev-host/dev-log-relay-plugin.ts +0 -510
- package/dist/dev-host/dev-runtime-config.ts +0 -14
- package/dist/dev-host/dev-runtime-platform.ts +0 -335
- package/dist/dev-host/dev-virtual-modules-plugin.ts +0 -64
- package/dist/dev-host/host-main.css +0 -224
- package/dist/dev-host/host-main.tsx +0 -948
- package/dist/dev-host/index.html +0 -56
- package/dist/dev-host/lib/utils.ts +0 -6
- package/dist/dev-host/plugin-main.ts +0 -61
- package/dist/dev-host/plugin.html +0 -24
- package/dist/dev-host/shared-styles.css +0 -144
- package/dist/dev-host/start-dev-server.ts +0 -140
- package/dist/dev-host/virtual-modules.d.ts +0 -27
- package/dist/global-config-S4ZIPECE.js.map +0 -1
- package/dist/keychain-backend-HDF4TZDL.js.map +0 -1
- package/skills/dreamboard/scripts/events-extract.mjs +0 -218
- /package/dist/agent-verifier/{chunk-SKI2ESE5.mjs.map → chunk-H6XDQJ3N.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-VS573ERH.mjs.map → chunk-JZTH3EMV.mjs.map} +0 -0
- /package/dist/agent-verifier/{global-config-Y2NTSK4R.mjs.map → global-config-2NUESNEQ.mjs.map} +0 -0
- /package/dist/agent-verifier/{local-files-JFOQQZDL.mjs.map → local-files-OF4QFISU.mjs.map} +0 -0
- /package/dist/agent-verifier/{local-typecheck-XVGWI75X.mjs.map → project-state-XKUSCFSV.mjs.map} +0 -0
- /package/dist/agent-verifier/{prompt-MJRJMOGQ.mjs.map → prompt-VKHMCQT6.mjs.map} +0 -0
- /package/dist/agent-verifier/{project-state-K576C2TE.mjs.map → static-scaffold-CLRRWXON.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-bundle-preflight-LXNJUBKL.mjs.map → workspace-codegen-SPPVHURX.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-contract-preflight-TUMQ43JV.mjs.map → workspace-dependencies-5HEEKZFP.mjs.map} +0 -0
- /package/dist/{agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs.map → authoring-compatibility-internal.js.map} +0 -0
- /package/dist/{agent-verifier/static-scaffold-R7SVDRQI.mjs.map → chunk-2H7UOFLK.js.map} +0 -0
- /package/dist/{agent-verifier/workspace-codegen-2ZMQRIKJ.mjs.map → global-config-RBMW7IVA.js.map} +0 -0
- /package/dist/{prompt-NDV3AE5L.js.map → prompt-GMZABCJC.js.map} +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.framework.json +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.json +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/index.tsx +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/style.css +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.framework.json +0 -0
- /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
|
-
}
|