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.
Files changed (186) hide show
  1. package/dist/{chunk-EYYWGWTO.js → chunk-2RCUHMGL.js} +343 -1911
  2. package/dist/chunk-2RCUHMGL.js.map +1 -0
  3. package/dist/{chunk-MOVHYB6E.js → chunk-GN7232BY.js} +1025 -695
  4. package/dist/chunk-GN7232BY.js.map +1 -0
  5. package/dist/{chunk-BMYC6772.js → chunk-H3O43F5P.js} +9765 -3068
  6. package/dist/chunk-H3O43F5P.js.map +1 -0
  7. package/dist/dev-host/dev-api-proxy-plugin.ts +330 -0
  8. package/dist/dev-host/dev-diagnostics.ts +62 -0
  9. package/dist/dev-host/dev-fallback-stylesheet.ts +50 -0
  10. package/dist/dev-host/dev-host-controller.ts +686 -0
  11. package/dist/dev-host/dev-host-player-query.ts +17 -0
  12. package/dist/dev-host/dev-host-session-transport.ts +52 -0
  13. package/dist/dev-host/dev-host-storage.ts +56 -0
  14. package/dist/dev-host/dev-log-relay-plugin.ts +469 -0
  15. package/dist/dev-host/dev-runtime-config.ts +14 -0
  16. package/dist/dev-host/dev-runtime-platform.ts +419 -0
  17. package/dist/dev-host/dev-virtual-modules-plugin.ts +63 -0
  18. package/dist/dev-host/host-main.css +182 -0
  19. package/dist/dev-host/host-main.tsx +754 -0
  20. package/dist/dev-host/index.html +56 -0
  21. package/dist/dev-host/plugin-main.ts +55 -0
  22. package/dist/dev-host/plugin.html +24 -0
  23. package/dist/dev-host/start-dev-server.ts +138 -0
  24. package/dist/dev-host/virtual-modules.d.ts +27 -0
  25. package/dist/dist-FEPN3BDN.js +16543 -0
  26. package/dist/dist-FEPN3BDN.js.map +1 -0
  27. package/dist/{dist-WJRJNFLI.js → dist-FRURQI7Q.js} +4 -2
  28. package/dist/index.js +3 -3
  29. package/dist/internal.js +159 -124
  30. package/dist/internal.js.map +1 -1
  31. package/dist/runtime-packages/tailwind-config/shared-styles.css +146 -0
  32. package/dist/runtime-packages/ui/src/components/.gitkeep +0 -0
  33. package/dist/runtime-packages/ui/src/components/accordion.tsx +66 -0
  34. package/dist/runtime-packages/ui/src/components/ai-elements/actions.tsx +65 -0
  35. package/dist/runtime-packages/ui/src/components/ai-elements/artifact.tsx +147 -0
  36. package/dist/runtime-packages/ui/src/components/ai-elements/branch.tsx +215 -0
  37. package/dist/runtime-packages/ui/src/components/ai-elements/canvas.tsx +22 -0
  38. package/dist/runtime-packages/ui/src/components/ai-elements/chain-of-thought.tsx +228 -0
  39. package/dist/runtime-packages/ui/src/components/ai-elements/code-block.tsx +179 -0
  40. package/dist/runtime-packages/ui/src/components/ai-elements/confirmation.tsx +158 -0
  41. package/dist/runtime-packages/ui/src/components/ai-elements/connection.tsx +28 -0
  42. package/dist/runtime-packages/ui/src/components/ai-elements/context.tsx +408 -0
  43. package/dist/runtime-packages/ui/src/components/ai-elements/controls.tsx +18 -0
  44. package/dist/runtime-packages/ui/src/components/ai-elements/conversation.tsx +97 -0
  45. package/dist/runtime-packages/ui/src/components/ai-elements/edge.tsx +140 -0
  46. package/dist/runtime-packages/ui/src/components/ai-elements/image.tsx +24 -0
  47. package/dist/runtime-packages/ui/src/components/ai-elements/inline-citation.tsx +287 -0
  48. package/dist/runtime-packages/ui/src/components/ai-elements/loader.tsx +96 -0
  49. package/dist/runtime-packages/ui/src/components/ai-elements/message.tsx +463 -0
  50. package/dist/runtime-packages/ui/src/components/ai-elements/node.tsx +71 -0
  51. package/dist/runtime-packages/ui/src/components/ai-elements/open-in-chat.tsx +365 -0
  52. package/dist/runtime-packages/ui/src/components/ai-elements/panel.tsx +15 -0
  53. package/dist/runtime-packages/ui/src/components/ai-elements/plan.tsx +142 -0
  54. package/dist/runtime-packages/ui/src/components/ai-elements/prompt-input.tsx +1380 -0
  55. package/dist/runtime-packages/ui/src/components/ai-elements/queue.tsx +274 -0
  56. package/dist/runtime-packages/ui/src/components/ai-elements/reasoning.tsx +182 -0
  57. package/dist/runtime-packages/ui/src/components/ai-elements/response.tsx +22 -0
  58. package/dist/runtime-packages/ui/src/components/ai-elements/shimmer.tsx +64 -0
  59. package/dist/runtime-packages/ui/src/components/ai-elements/sources.tsx +77 -0
  60. package/dist/runtime-packages/ui/src/components/ai-elements/suggestion.tsx +53 -0
  61. package/dist/runtime-packages/ui/src/components/ai-elements/task.tsx +87 -0
  62. package/dist/runtime-packages/ui/src/components/ai-elements/tool.tsx +165 -0
  63. package/dist/runtime-packages/ui/src/components/ai-elements/toolbar.tsx +16 -0
  64. package/dist/runtime-packages/ui/src/components/ai-elements/web-preview.tsx +263 -0
  65. package/dist/runtime-packages/ui/src/components/alert-dialog.tsx +157 -0
  66. package/dist/runtime-packages/ui/src/components/alert.tsx +66 -0
  67. package/dist/runtime-packages/ui/src/components/avatar.tsx +53 -0
  68. package/dist/runtime-packages/ui/src/components/badge.tsx +46 -0
  69. package/dist/runtime-packages/ui/src/components/button-group.tsx +83 -0
  70. package/dist/runtime-packages/ui/src/components/button.tsx +65 -0
  71. package/dist/runtime-packages/ui/src/components/card.tsx +92 -0
  72. package/dist/runtime-packages/ui/src/components/carousel.tsx +241 -0
  73. package/dist/runtime-packages/ui/src/components/collapsible.tsx +33 -0
  74. package/dist/runtime-packages/ui/src/components/command.tsx +184 -0
  75. package/dist/runtime-packages/ui/src/components/context-menu.tsx +252 -0
  76. package/dist/runtime-packages/ui/src/components/counter.tsx +11 -0
  77. package/dist/runtime-packages/ui/src/components/dialog.tsx +146 -0
  78. package/dist/runtime-packages/ui/src/components/drawer.tsx +132 -0
  79. package/dist/runtime-packages/ui/src/components/dropdown-menu.tsx +257 -0
  80. package/dist/runtime-packages/ui/src/components/form.tsx +168 -0
  81. package/dist/runtime-packages/ui/src/components/header.tsx +13 -0
  82. package/dist/runtime-packages/ui/src/components/hover-card.tsx +44 -0
  83. package/dist/runtime-packages/ui/src/components/input-group.tsx +178 -0
  84. package/dist/runtime-packages/ui/src/components/input.tsx +21 -0
  85. package/dist/runtime-packages/ui/src/components/item.tsx +193 -0
  86. package/dist/runtime-packages/ui/src/components/label.tsx +24 -0
  87. package/dist/runtime-packages/ui/src/components/navigation-menu.tsx +168 -0
  88. package/dist/runtime-packages/ui/src/components/popover.tsx +55 -0
  89. package/dist/runtime-packages/ui/src/components/progress.tsx +31 -0
  90. package/dist/runtime-packages/ui/src/components/radio-group.tsx +45 -0
  91. package/dist/runtime-packages/ui/src/components/resizable.tsx +56 -0
  92. package/dist/runtime-packages/ui/src/components/scroll-area.tsx +58 -0
  93. package/dist/runtime-packages/ui/src/components/select.tsx +187 -0
  94. package/dist/runtime-packages/ui/src/components/separator.tsx +28 -0
  95. package/dist/runtime-packages/ui/src/components/skeleton.tsx +13 -0
  96. package/dist/runtime-packages/ui/src/components/sonner.tsx +25 -0
  97. package/dist/runtime-packages/ui/src/components/spinner.tsx +16 -0
  98. package/dist/runtime-packages/ui/src/components/switch.tsx +31 -0
  99. package/dist/runtime-packages/ui/src/components/table.tsx +137 -0
  100. package/dist/runtime-packages/ui/src/components/tabs.tsx +74 -0
  101. package/dist/runtime-packages/ui/src/components/textarea.tsx +23 -0
  102. package/dist/runtime-packages/ui/src/components/theme-provider.tsx +73 -0
  103. package/dist/runtime-packages/ui/src/components/tooltip.tsx +61 -0
  104. package/dist/runtime-packages/ui/src/components/ui/file-upload.tsx +195 -0
  105. package/dist/runtime-packages/ui/src/hooks/.gitkeep +0 -0
  106. package/dist/runtime-packages/ui/src/index.ts +68 -0
  107. package/dist/runtime-packages/ui/src/lib/utils.ts +6 -0
  108. package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +68 -0
  109. package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +359 -0
  110. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +274 -0
  111. package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +214 -0
  112. package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +135 -0
  113. package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +5 -0
  114. package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +194 -0
  115. package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +1 -0
  116. package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +1 -0
  117. package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +162 -0
  118. package/dist/runtime-packages/ui-host-runtime/src/index.ts +2 -0
  119. package/dist/runtime-packages/ui-host-runtime/src/logger.ts +11 -0
  120. package/dist/runtime-packages/ui-host-runtime/src/perf.ts +253 -0
  121. package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +195 -0
  122. package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +138 -0
  123. package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +159 -0
  124. package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +524 -0
  125. package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +12 -0
  126. package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +122 -0
  127. package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +26 -0
  128. package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +477 -0
  129. package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +209 -0
  130. package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +112 -0
  131. package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +318 -0
  132. package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +211 -0
  133. package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +814 -0
  134. package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +334 -0
  135. package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +180 -0
  136. package/dist/scaffold/assets/static/app/tsconfig.framework.json +3 -1
  137. package/dist/scaffold/assets/static/ui/tsconfig.framework.json +7 -19
  138. package/package.json +52 -17
  139. package/skills/dreamboard/SKILL.md +41 -75
  140. package/LICENSE +0 -89
  141. package/NOTICE +0 -1
  142. package/dist/chunk-BMYC6772.js.map +0 -1
  143. package/dist/chunk-EYYWGWTO.js.map +0 -1
  144. package/dist/chunk-MOVHYB6E.js.map +0 -1
  145. package/dist/scaffold/assets/static/ui/App.tsx +0 -22
  146. package/dist/src-CUL7EGGG.js +0 -634
  147. package/dist/src-CUL7EGGG.js.map +0 -1
  148. package/skills/dreamboard/references/authoring-lifecycle.md +0 -102
  149. package/skills/dreamboard/references/board-surfaces.md +0 -36
  150. package/skills/dreamboard/references/board-topology.md +0 -443
  151. package/skills/dreamboard/references/boards-and-topology.md +0 -100
  152. package/skills/dreamboard/references/card-actions.md +0 -107
  153. package/skills/dreamboard/references/cli.md +0 -120
  154. package/skills/dreamboard/references/core-concepts.md +0 -514
  155. package/skills/dreamboard/references/custom-renderers.md +0 -26
  156. package/skills/dreamboard/references/derived-values.md +0 -55
  157. package/skills/dreamboard/references/effects.md +0 -111
  158. package/skills/dreamboard/references/game-contract.md +0 -89
  159. package/skills/dreamboard/references/game-definition.md +0 -89
  160. package/skills/dreamboard/references/game-shell.md +0 -80
  161. package/skills/dreamboard/references/hand-surfaces.md +0 -33
  162. package/skills/dreamboard/references/index.md +0 -112
  163. package/skills/dreamboard/references/inputs-and-targets.md +0 -160
  164. package/skills/dreamboard/references/interactions.md +0 -158
  165. package/skills/dreamboard/references/manifest-fields.md +0 -137
  166. package/skills/dreamboard/references/manifest.md +0 -81
  167. package/skills/dreamboard/references/package-surfaces.md +0 -69
  168. package/skills/dreamboard/references/phases.md +0 -145
  169. package/skills/dreamboard/references/prompts-and-choices.md +0 -55
  170. package/skills/dreamboard/references/quickstart.md +0 -71
  171. package/skills/dreamboard/references/rule-authoring.md +0 -144
  172. package/skills/dreamboard/references/setup-bootstrap.md +0 -78
  173. package/skills/dreamboard/references/stages-and-zones.md +0 -59
  174. package/skills/dreamboard/references/static-views.md +0 -67
  175. package/skills/dreamboard/references/table-queries-and-ops.md +0 -103
  176. package/skills/dreamboard/references/testing-bases.md +0 -147
  177. package/skills/dreamboard/references/testing-generated-contracts.md +0 -142
  178. package/skills/dreamboard/references/testing-runtime-assertions.md +0 -124
  179. package/skills/dreamboard/references/testing-scenarios.md +0 -148
  180. package/skills/dreamboard/references/testing-ui-tests.md +0 -174
  181. package/skills/dreamboard/references/testing.md +0 -161
  182. package/skills/dreamboard/references/ui-architecture.md +0 -137
  183. package/skills/dreamboard/references/ui-components.md +0 -34
  184. package/skills/dreamboard/references/views.md +0 -72
  185. package/skills/dreamboard/references/workspace-layout.md +0 -136
  186. /package/dist/{dist-WJRJNFLI.js.map → dist-FRURQI7Q.js.map} +0 -0
@@ -0,0 +1,52 @@
1
+ import {
2
+ defaultHostSessionTransport,
3
+ type HostSessionTransport,
4
+ } from "@dreamboard/ui-host-runtime/runtime";
5
+
6
+ type HostSessionSnapshot = Awaited<
7
+ ReturnType<HostSessionTransport["loadSessionSnapshot"]>
8
+ >;
9
+
10
+ type DevHostSessionSnapshot = HostSessionSnapshot & {
11
+ context: HostSessionSnapshot["context"] & { seed?: number | null };
12
+ };
13
+
14
+ async function requestSnapshot(
15
+ path: string,
16
+ init: RequestInit,
17
+ ): Promise<DevHostSessionSnapshot> {
18
+ const response = await fetch(path, init);
19
+ const contentType = response.headers.get("content-type") ?? "";
20
+ const payload = contentType.includes("application/json")
21
+ ? await response.json()
22
+ : await response.text();
23
+ if (!response.ok) {
24
+ throw payload;
25
+ }
26
+ return payload as DevHostSessionSnapshot;
27
+ }
28
+
29
+ export function createDevHostSessionTransport(): HostSessionTransport {
30
+ return {
31
+ ...defaultHostSessionTransport,
32
+ loadSessionSnapshot: async (input) =>
33
+ requestSnapshot(
34
+ input.requestedPlayerId?.trim()
35
+ ? `/__dreamboard_dev/session/snapshot?playerId=${encodeURIComponent(input.requestedPlayerId.trim())}`
36
+ : "/__dreamboard_dev/session/snapshot",
37
+ { method: "GET" },
38
+ ),
39
+ startSession: async () =>
40
+ requestSnapshot("/__dreamboard_dev/session/start", {
41
+ method: "POST",
42
+ body: undefined,
43
+ headers: undefined,
44
+ }),
45
+ createDevSessionSnapshot: async (input) =>
46
+ requestSnapshot("/__dreamboard_dev/session/new", {
47
+ method: "POST",
48
+ body: JSON.stringify({ seed: input.seed }),
49
+ headers: { "content-type": "application/json" },
50
+ }),
51
+ };
52
+ }
@@ -0,0 +1,56 @@
1
+ export type ActiveSession = {
2
+ sessionId: string;
3
+ shortCode: string;
4
+ gameId: string;
5
+ seed: number | null;
6
+ };
7
+
8
+ export interface DevHostStorage {
9
+ loadSidebarOpen(): boolean;
10
+ persistSidebarOpen(open: boolean): void;
11
+ loadPreferredPlayerId(): string | null;
12
+ persistPreferredPlayerId(playerId: string | null): void;
13
+ }
14
+
15
+ const SIDEBAR_STORAGE_KEY = "dreamboard-dev-sidebar";
16
+ const PREFERRED_PLAYER_STORAGE_KEY = "dreamboard-dev-preferred-player";
17
+
18
+ export class SessionStorageDevHostStorage implements DevHostStorage {
19
+ constructor(private readonly storage: Storage) {}
20
+
21
+ loadSidebarOpen(): boolean {
22
+ try {
23
+ return this.storage.getItem(SIDEBAR_STORAGE_KEY) === "true";
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ persistSidebarOpen(open: boolean): void {
30
+ try {
31
+ this.storage.setItem(SIDEBAR_STORAGE_KEY, String(open));
32
+ } catch {
33
+ // Ignore persistence failures in locked-down browser contexts.
34
+ }
35
+ }
36
+
37
+ loadPreferredPlayerId(): string | null {
38
+ try {
39
+ return this.storage.getItem(PREFERRED_PLAYER_STORAGE_KEY)?.trim() || null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ persistPreferredPlayerId(playerId: string | null): void {
46
+ try {
47
+ if (playerId?.trim()) {
48
+ this.storage.setItem(PREFERRED_PLAYER_STORAGE_KEY, playerId.trim());
49
+ } else {
50
+ this.storage.removeItem(PREFERRED_PLAYER_STORAGE_KEY);
51
+ }
52
+ } catch {
53
+ // Ignore persistence failures in locked-down browser contexts.
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,469 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import consola from "consola";
3
+ import type { Plugin } from "vite";
4
+ import type { ResolvedConfig } from "../types.js";
5
+ import { createPersistedDevSession } from "../utils/dev-session.js";
6
+ import { exists, readJsonFile, writeJsonFile } from "../utils/fs.js";
7
+ import { resolveDevBearer } from "./dev-api-proxy-plugin.js";
8
+ import {
9
+ shouldRelayDevLog,
10
+ type DevDiagnosticsLevel,
11
+ type DevLogEnvelope,
12
+ } from "./dev-diagnostics.js";
13
+ import type { DreamboardDevRuntimeConfig } from "./dev-runtime-config.js";
14
+ import type { ActiveSession } from "./dev-host-storage.js";
15
+
16
+ export function createDevLogRelayPlugin(options: {
17
+ sessionFilePath: string;
18
+ runtimeConfig: DreamboardDevRuntimeConfig;
19
+ config: ResolvedConfig;
20
+ diagnosticsLevel: DevDiagnosticsLevel;
21
+ }): Plugin {
22
+ return {
23
+ name: "dreamboard-dev-log-relay",
24
+ configureServer(server) {
25
+ server.middlewares.use("/__dreamboard_dev/log", (req, res) => {
26
+ if (req.method !== "POST") {
27
+ res.statusCode = 405;
28
+ res.setHeader("content-type", "application/json");
29
+ res.end(JSON.stringify({ error: "Method not allowed" }));
30
+ return;
31
+ }
32
+
33
+ const chunks: Buffer[] = [];
34
+ req.on("data", (chunk) => {
35
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
36
+ });
37
+ req.on("end", () => {
38
+ try {
39
+ const body = Buffer.concat(chunks).toString("utf8");
40
+ const payload = JSON.parse(body) as Partial<DevLogEnvelope>;
41
+ const normalizedPayload = {
42
+ source: coerceLogSource(payload.source),
43
+ level: coerceLogLevel(payload.level),
44
+ message:
45
+ typeof payload.message === "string"
46
+ ? payload.message
47
+ : "Missing dev log message",
48
+ } satisfies DevLogEnvelope;
49
+ if (
50
+ shouldRelayDevLog(options.diagnosticsLevel, normalizedPayload)
51
+ ) {
52
+ relayDevLog(normalizedPayload);
53
+ }
54
+ res.statusCode = 204;
55
+ res.end();
56
+ } catch (error) {
57
+ consola.warn(
58
+ `[dev-host] Failed to decode browser log payload: ${formatUnknown(error)}`,
59
+ );
60
+ res.statusCode = 400;
61
+ res.setHeader("content-type", "application/json");
62
+ res.end(JSON.stringify({ error: "Invalid dev log payload" }));
63
+ }
64
+ });
65
+ });
66
+
67
+ server.middlewares.use(
68
+ "/__dreamboard_dev/session/snapshot",
69
+ createSnapshotSessionHandler({
70
+ sessionFilePath: options.sessionFilePath,
71
+ runtimeConfig: options.runtimeConfig,
72
+ config: options.config,
73
+ }),
74
+ );
75
+ server.middlewares.use(
76
+ "/__dreamboard_dev/session/new",
77
+ createNewSessionHandler({
78
+ sessionFilePath: options.sessionFilePath,
79
+ runtimeConfig: options.runtimeConfig,
80
+ config: options.config,
81
+ }),
82
+ );
83
+ server.middlewares.use(
84
+ "/__dreamboard_dev/session/start",
85
+ createStartSessionHandler({
86
+ sessionFilePath: options.sessionFilePath,
87
+ runtimeConfig: options.runtimeConfig,
88
+ config: options.config,
89
+ }),
90
+ );
91
+ },
92
+ };
93
+ }
94
+
95
+ export function createSnapshotSessionHandler(options: {
96
+ sessionFilePath: string;
97
+ runtimeConfig: DreamboardDevRuntimeConfig;
98
+ config: ResolvedConfig;
99
+ }): (req: IncomingMessage, res: ServerResponse) => void {
100
+ return (req, res) => {
101
+ void handleSnapshotSessionRequest(req, res, options);
102
+ };
103
+ }
104
+
105
+ export function createNewSessionHandler(options: {
106
+ sessionFilePath: string;
107
+ runtimeConfig: DreamboardDevRuntimeConfig;
108
+ config: ResolvedConfig;
109
+ }): (req: IncomingMessage, res: ServerResponse) => void {
110
+ return (req, res) => {
111
+ void handleNewSessionRequest(req, res, options);
112
+ };
113
+ }
114
+
115
+ export function createStartSessionHandler(options: {
116
+ sessionFilePath: string;
117
+ runtimeConfig: DreamboardDevRuntimeConfig;
118
+ config: ResolvedConfig;
119
+ }): (req: IncomingMessage, res: ServerResponse) => void {
120
+ return (req, res) => {
121
+ void handleStartSessionRequest(req, res, options);
122
+ };
123
+ }
124
+
125
+ async function handleSnapshotSessionRequest(
126
+ req: IncomingMessage,
127
+ res: ServerResponse,
128
+ options: {
129
+ sessionFilePath: string;
130
+ runtimeConfig: DreamboardDevRuntimeConfig;
131
+ config: ResolvedConfig;
132
+ },
133
+ ): Promise<void> {
134
+ if (req.method !== "GET") {
135
+ respondJson(res, 405, { error: "Method not allowed" });
136
+ return;
137
+ }
138
+
139
+ try {
140
+ const session = await loadCurrentSession(options);
141
+ const requestedPlayerId = extractQueryParam(req, "playerId");
142
+ let snapshot = await fetchBackendJson(
143
+ options.config,
144
+ appendQuery(`/api/sessions/${session.sessionId}/snapshot`, {
145
+ playerId: requestedPlayerId,
146
+ }),
147
+ );
148
+ if (
149
+ options.runtimeConfig.autoStartGame &&
150
+ isStartableLobbySnapshot(snapshot)
151
+ ) {
152
+ snapshot = await fetchBackendJson(
153
+ options.config,
154
+ `/api/sessions/${session.sessionId}/start`,
155
+ { method: "POST" },
156
+ );
157
+ if (requestedPlayerId) {
158
+ snapshot = await fetchBackendJson(
159
+ options.config,
160
+ appendQuery(`/api/sessions/${session.sessionId}/snapshot`, {
161
+ playerId: requestedPlayerId,
162
+ }),
163
+ );
164
+ }
165
+ await persistSessionId(options.sessionFilePath, session.sessionId);
166
+ }
167
+ respondJson(res, 200, attachLocalSeed(snapshot, session.seed ?? null));
168
+ } catch (error) {
169
+ respondJson(res, statusForError(error), { error: formatUnknown(error) });
170
+ }
171
+ }
172
+
173
+ async function handleNewSessionRequest(
174
+ req: IncomingMessage,
175
+ res: ServerResponse,
176
+ options: {
177
+ sessionFilePath: string;
178
+ runtimeConfig: DreamboardDevRuntimeConfig;
179
+ config: ResolvedConfig;
180
+ },
181
+ ): Promise<void> {
182
+ if (req.method !== "POST") {
183
+ respondJson(res, 405, { error: "Method not allowed" });
184
+ return;
185
+ }
186
+
187
+ try {
188
+ const body = await readJsonBody(req);
189
+ const seed = Number.parseInt(String(body.seed ?? ""), 10);
190
+ if (!Number.isSafeInteger(seed)) {
191
+ throw new Error("Seed must be a safe integer.");
192
+ }
193
+ const created = await fetchBackendJson(
194
+ options.config,
195
+ `/api/games/${options.runtimeConfig.gameId}/sessions`,
196
+ {
197
+ method: "POST",
198
+ body: {
199
+ compiledResultId: options.runtimeConfig.compiledResultId,
200
+ seed,
201
+ playerCount: options.runtimeConfig.playerCount,
202
+ autoAssignSeats: true,
203
+ setupProfileId: options.runtimeConfig.setupProfileId ?? undefined,
204
+ },
205
+ },
206
+ );
207
+ const sessionId = requireString(
208
+ (created as { sessionId?: unknown }).sessionId,
209
+ "sessionId",
210
+ );
211
+ let snapshot = await fetchBackendJson(
212
+ options.config,
213
+ `/api/sessions/${sessionId}/snapshot`,
214
+ );
215
+ if (
216
+ options.runtimeConfig.autoStartGame &&
217
+ isStartableLobbySnapshot(snapshot)
218
+ ) {
219
+ snapshot = await fetchBackendJson(
220
+ options.config,
221
+ `/api/sessions/${sessionId}/start`,
222
+ {
223
+ method: "POST",
224
+ },
225
+ );
226
+ }
227
+ await persistSessionId(options.sessionFilePath, sessionId);
228
+ respondJson(res, 200, attachLocalSeed(snapshot, seed));
229
+ } catch (error) {
230
+ respondJson(res, statusForError(error), { error: formatUnknown(error) });
231
+ }
232
+ }
233
+
234
+ async function handleStartSessionRequest(
235
+ req: IncomingMessage,
236
+ res: ServerResponse,
237
+ options: {
238
+ sessionFilePath: string;
239
+ runtimeConfig: DreamboardDevRuntimeConfig;
240
+ config: ResolvedConfig;
241
+ },
242
+ ): Promise<void> {
243
+ if (req.method !== "POST") {
244
+ respondJson(res, 405, { error: "Method not allowed" });
245
+ return;
246
+ }
247
+
248
+ try {
249
+ const session = await loadCurrentSession(options);
250
+ const snapshot = await fetchBackendJson(
251
+ options.config,
252
+ `/api/sessions/${session.sessionId}/start`,
253
+ { method: "POST" },
254
+ );
255
+ await persistSessionId(options.sessionFilePath, session.sessionId);
256
+ respondJson(res, 200, attachLocalSeed(snapshot, session.seed ?? null));
257
+ } catch (error) {
258
+ respondJson(res, statusForError(error), { error: formatUnknown(error) });
259
+ }
260
+ }
261
+
262
+ async function readJsonBody(
263
+ req: IncomingMessage,
264
+ ): Promise<Record<string, unknown>> {
265
+ const chunks: Buffer[] = [];
266
+ await new Promise<void>((resolve, reject) => {
267
+ req.on("data", (chunk) => {
268
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
269
+ });
270
+ req.on("end", () => resolve());
271
+ req.on("error", reject);
272
+ });
273
+ const text = Buffer.concat(chunks).toString("utf8");
274
+ return text ? (JSON.parse(text) as Record<string, unknown>) : {};
275
+ }
276
+
277
+ function requireString(value: unknown, field: string): string {
278
+ if (typeof value !== "string" || value.length === 0) {
279
+ throw new Error(`Missing required field: ${field}`);
280
+ }
281
+ return value;
282
+ }
283
+
284
+ function extractQueryParam(req: IncomingMessage, name: string): string | null {
285
+ const rawUrl = req.url ?? "";
286
+ const value = new URL(rawUrl, "http://dreamboard.local").searchParams.get(
287
+ name,
288
+ );
289
+ return value?.trim() || null;
290
+ }
291
+
292
+ function appendQuery(
293
+ path: string,
294
+ query: Record<string, string | null | undefined>,
295
+ ): string {
296
+ const params = new URLSearchParams();
297
+ for (const [key, value] of Object.entries(query)) {
298
+ if (value) {
299
+ params.set(key, value);
300
+ }
301
+ }
302
+ const serialized = params.toString();
303
+ return serialized ? `${path}?${serialized}` : path;
304
+ }
305
+
306
+ async function loadCurrentSession(options: {
307
+ sessionFilePath: string;
308
+ runtimeConfig: DreamboardDevRuntimeConfig;
309
+ }): Promise<ActiveSession> {
310
+ if (!(await exists(options.sessionFilePath))) {
311
+ return options.runtimeConfig.initialSession;
312
+ }
313
+
314
+ const payload = await readJsonFile<unknown>(options.sessionFilePath);
315
+ const session = parsePersistedSessionPointer(payload, options.runtimeConfig);
316
+ if (!session) {
317
+ throw new Error("Session file did not contain a valid session pointer.");
318
+ }
319
+ return session;
320
+ }
321
+
322
+ async function persistSessionId(
323
+ sessionFilePath: string,
324
+ sessionId: string,
325
+ ): Promise<void> {
326
+ await writeJsonFile(
327
+ sessionFilePath,
328
+ createPersistedDevSession({ sessionId }),
329
+ );
330
+ }
331
+
332
+ async function fetchBackendJson(
333
+ config: ResolvedConfig,
334
+ path: string,
335
+ options: {
336
+ method?: "GET" | "POST";
337
+ body?: Record<string, unknown>;
338
+ } = {},
339
+ ): Promise<unknown> {
340
+ const bearer = await resolveDevBearer(config);
341
+ if (bearer.kind === "permanent_invalid") {
342
+ throw new HttpError(401, bearer.message);
343
+ }
344
+
345
+ const response = await fetch(`${config.apiBaseUrl}${path}`, {
346
+ method: options.method ?? "GET",
347
+ headers: {
348
+ ...(bearer.token ? { authorization: `Bearer ${bearer.token}` } : {}),
349
+ ...(options.body ? { "content-type": "application/json" } : {}),
350
+ },
351
+ body: options.body ? JSON.stringify(options.body) : undefined,
352
+ });
353
+ if (!response.ok) {
354
+ const text = await response.text().catch(() => "");
355
+ throw new HttpError(
356
+ response.status,
357
+ text || `Backend request failed with ${response.status}`,
358
+ );
359
+ }
360
+ return response.json();
361
+ }
362
+
363
+ function attachLocalSeed(snapshot: unknown, seed: number | null): unknown {
364
+ if (!snapshot || typeof snapshot !== "object") {
365
+ return snapshot;
366
+ }
367
+ const context = (snapshot as { context?: unknown }).context;
368
+ if (!context || typeof context !== "object") {
369
+ return snapshot;
370
+ }
371
+ return {
372
+ ...(snapshot as Record<string, unknown>),
373
+ context: {
374
+ ...(context as Record<string, unknown>),
375
+ seed,
376
+ },
377
+ };
378
+ }
379
+
380
+ function isStartableLobbySnapshot(snapshot: unknown): boolean {
381
+ if (!snapshot || typeof snapshot !== "object") {
382
+ return false;
383
+ }
384
+ const context = (snapshot as { context?: { phase?: unknown } }).context;
385
+ const lobby = (snapshot as { lobby?: { canStart?: unknown } }).lobby;
386
+ return context?.phase === "lobby" && lobby?.canStart === true;
387
+ }
388
+
389
+ function respondJson(
390
+ res: ServerResponse,
391
+ statusCode: number,
392
+ payload: unknown,
393
+ ): void {
394
+ res.statusCode = statusCode;
395
+ res.setHeader("content-type", "application/json");
396
+ res.end(JSON.stringify(payload));
397
+ }
398
+
399
+ function statusForError(error: unknown): number {
400
+ return error instanceof HttpError ? error.statusCode : 500;
401
+ }
402
+
403
+ class HttpError extends Error {
404
+ constructor(
405
+ readonly statusCode: number,
406
+ message: string,
407
+ ) {
408
+ super(message);
409
+ }
410
+ }
411
+
412
+ function parsePersistedSessionPointer(
413
+ value: unknown,
414
+ runtimeConfig: DreamboardDevRuntimeConfig,
415
+ ): DreamboardDevRuntimeConfig["initialSession"] | null {
416
+ if (!value || typeof value !== "object") {
417
+ return null;
418
+ }
419
+ const sessionId = (value as { sessionId?: unknown }).sessionId;
420
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
421
+ return null;
422
+ }
423
+ return {
424
+ sessionId,
425
+ shortCode: "Unknown",
426
+ gameId: runtimeConfig.gameId,
427
+ seed: null,
428
+ };
429
+ }
430
+
431
+ function relayDevLog(payload: DevLogEnvelope): void {
432
+ const formatted = `[dev:${payload.source}] ${payload.message}`;
433
+ switch (payload.level) {
434
+ case "error":
435
+ consola.error(formatted);
436
+ break;
437
+ case "warn":
438
+ consola.warn(formatted);
439
+ break;
440
+ case "info":
441
+ consola.info(formatted);
442
+ break;
443
+ default:
444
+ consola.log(formatted);
445
+ break;
446
+ }
447
+ }
448
+
449
+ function coerceLogSource(value: unknown): DevLogEnvelope["source"] {
450
+ return value === "host" || value === "plugin" || value === "sse"
451
+ ? value
452
+ : "host";
453
+ }
454
+
455
+ function coerceLogLevel(value: unknown): DevLogEnvelope["level"] {
456
+ return value === "warn" ||
457
+ value === "error" ||
458
+ value === "info" ||
459
+ value === "log"
460
+ ? value
461
+ : "log";
462
+ }
463
+
464
+ function formatUnknown(value: unknown): string {
465
+ if (value instanceof Error) {
466
+ return value.stack ?? value.message;
467
+ }
468
+ return typeof value === "string" ? value : JSON.stringify(value);
469
+ }
@@ -0,0 +1,14 @@
1
+ import type { ActiveSession } from "./dev-host-storage.js";
2
+
3
+ export interface DreamboardDevRuntimeConfig {
4
+ apiBaseUrl: string;
5
+ userId: string | null;
6
+ gameId: string;
7
+ compiledResultId: string;
8
+ setupProfileId: string | null;
9
+ playerCount: number;
10
+ debug: boolean;
11
+ slug: string;
12
+ autoStartGame: boolean;
13
+ initialSession: ActiveSession;
14
+ }