dreamboard 0.1.21 → 0.1.22

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-AQ5UUNJS.js} +1018 -692
  4. package/dist/chunk-AQ5UUNJS.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,524 @@
1
+ import type { PluginStateSnapshot } from "@dreamboard/ui-sdk/reducer";
2
+ import type { ValidationResult } from "@dreamboard/ui-sdk/types/runtime-api";
3
+ import { PluginBridge } from "./plugin-bridge.js";
4
+ import { PluginHealthCheck } from "./plugin-health-check.js";
5
+ import type { LoggerLike } from "./logger.js";
6
+ import { consoleLogger } from "./logger.js";
7
+ import { PERF_MARK_NAMES, findActionIdBySyncId, recordMark } from "./perf.js";
8
+
9
+ export interface GameSessionStoreApi {
10
+ getStateSnapshot: () => PluginStateSnapshot;
11
+ subscribe: (callback: () => void) => () => void;
12
+ onStateAck: (syncId: number) => void;
13
+ markNotificationRead: (notificationId: string) => void;
14
+ }
15
+
16
+ export type GatewayState = "loading" | "handshaking" | "connected" | "error";
17
+
18
+ /**
19
+ * Per-interaction metadata the gateway forwards from the plugin down to
20
+ * `onInteraction`. Currently carries just the client-minted correlation id
21
+ * used by Tier-0 input-latency observability; keeping it as a bag lets
22
+ * future perf/debug fields (e.g. plugin-side timestamps) ride along
23
+ * without another signature change.
24
+ */
25
+ export interface InteractionMeta {
26
+ clientActionId?: string;
27
+ }
28
+
29
+ export interface PluginSessionGatewayConfig {
30
+ iframe: HTMLIFrameElement;
31
+ sessionId: string;
32
+ controllablePlayerIds: string[];
33
+ controllingPlayerId: string;
34
+ userId: string | null;
35
+ onReady: () => void;
36
+ onError: (error: Error) => void;
37
+ onInteraction: (
38
+ playerId: string,
39
+ interactionId: string,
40
+ params: unknown,
41
+ meta?: InteractionMeta,
42
+ ) => void | Promise<void>;
43
+ onValidateInteraction: (
44
+ playerId: string,
45
+ interactionId: string,
46
+ params: unknown,
47
+ ) => Promise<ValidationResult>;
48
+ onSwitchPlayer?: (playerId: string) => void;
49
+ onRestoreHistory?: (entryId: string) => void;
50
+ logger?: LoggerLike;
51
+ }
52
+
53
+ export class PluginSessionGateway {
54
+ private bridge: PluginBridge | null = null;
55
+ private healthCheck: PluginHealthCheck | null = null;
56
+ private state: GatewayState = "loading";
57
+ private unsubscribeHandlers: Array<() => void> = [];
58
+ private config: PluginSessionGatewayConfig;
59
+ private readyTimeout: ReturnType<typeof setTimeout> | null = null;
60
+ private initStartTimeout: ReturnType<typeof setTimeout> | null = null;
61
+ private initRetryInterval: ReturnType<typeof setInterval> | null = null;
62
+ private storeUnsubscribe: (() => void) | null = null;
63
+ private gameSessionStore: GameSessionStoreApi | null = null;
64
+ private lastSentSyncId: number | null = null;
65
+ private logger: LoggerLike;
66
+
67
+ constructor(config: PluginSessionGatewayConfig) {
68
+ this.config = config;
69
+ this.logger = config.logger ?? consoleLogger;
70
+ }
71
+
72
+ connect(): void {
73
+ if (this.bridge) {
74
+ this.logger.warn("[Gateway] Already connected");
75
+ return;
76
+ }
77
+
78
+ this.state = "loading";
79
+
80
+ const targetOrigin = "*";
81
+ this.bridge = new PluginBridge(this.config.iframe, targetOrigin, {
82
+ logger: this.logger,
83
+ });
84
+
85
+ this.readyTimeout = setTimeout(() => {
86
+ if (this.state !== "connected") {
87
+ this.handleError(
88
+ new Error("Plugin failed to send ready message within 10 seconds"),
89
+ );
90
+ }
91
+ }, 10000);
92
+
93
+ this.setupInteractionHandler();
94
+ this.setupValidateInteractionHandler();
95
+ this.setupReadyHandler();
96
+ this.setupErrorHandler();
97
+ this.setupSwitchPlayerHandler();
98
+ this.setupRestoreHistoryHandler();
99
+ this.setupStateAckHandler();
100
+ this.setupStateRenderedHandler();
101
+ this.setupMarkNotificationReadHandler();
102
+
103
+ this.state = "handshaking";
104
+
105
+ this.initStartTimeout = setTimeout(() => {
106
+ this.initStartTimeout = null;
107
+ if (this.state === "error") {
108
+ return;
109
+ }
110
+ this.sendInit();
111
+ this.initRetryInterval = setInterval(() => {
112
+ if (this.state === "connected") {
113
+ this.clearInitRetryInterval();
114
+ return;
115
+ }
116
+ this.sendInit();
117
+ }, 250);
118
+ }, 50);
119
+ }
120
+
121
+ attachStore(store: GameSessionStoreApi): void {
122
+ if (this.state !== "connected") {
123
+ this.logger.warn(
124
+ "[Gateway] Cannot attach store - plugin not ready yet (state: " +
125
+ this.state +
126
+ ")",
127
+ );
128
+ return;
129
+ }
130
+
131
+ if (this.storeUnsubscribe) {
132
+ this.logger.warn("[Gateway] Store already attached");
133
+ return;
134
+ }
135
+
136
+ this.gameSessionStore = store;
137
+
138
+ const sendStateSync = () => {
139
+ if (!this.bridge) return;
140
+
141
+ const snapshot = store.getStateSnapshot();
142
+
143
+ if (snapshot.syncId !== this.lastSentSyncId) {
144
+ this.lastSentSyncId = snapshot.syncId;
145
+ this.logger.log("[Gateway] State snapshot view present:", {
146
+ syncId: snapshot.syncId,
147
+ hasView: snapshot.view !== null,
148
+ controllingPlayerId: snapshot.session.controllingPlayerId,
149
+ controllablePlayerIds: snapshot.session.controllablePlayerIds,
150
+ });
151
+ this.bridge.sendStateSync(snapshot.syncId, snapshot);
152
+ // Tier-0 perf: stitch the outgoing state-sync to the action
153
+ // that caused it (via the syncId -> actionId map populated
154
+ // at `t5_store_applied`). For non-action-driven syncs (lobby
155
+ // updates etc.) the lookup returns undefined and the mark is
156
+ // a no-op.
157
+ const actionId = findActionIdBySyncId(snapshot.syncId);
158
+ if (actionId) {
159
+ recordMark(actionId, PERF_MARK_NAMES.T6_STATE_SYNC_POSTED, {
160
+ extra: { syncId: snapshot.syncId },
161
+ });
162
+ }
163
+ this.logger.log("[Gateway] Sent state-sync, syncId:", snapshot.syncId);
164
+ }
165
+ };
166
+
167
+ sendStateSync();
168
+
169
+ this.storeUnsubscribe = store.subscribe(() => {
170
+ sendStateSync();
171
+ });
172
+ }
173
+
174
+ getState(): GatewayState {
175
+ return this.state;
176
+ }
177
+
178
+ disconnect(): void {
179
+ if (this.readyTimeout) {
180
+ clearTimeout(this.readyTimeout);
181
+ this.readyTimeout = null;
182
+ }
183
+
184
+ if (this.initStartTimeout) {
185
+ clearTimeout(this.initStartTimeout);
186
+ this.initStartTimeout = null;
187
+ }
188
+
189
+ this.clearInitRetryInterval();
190
+
191
+ if (this.healthCheck) {
192
+ this.healthCheck.stop();
193
+ this.healthCheck = null;
194
+ }
195
+
196
+ if (this.bridge) {
197
+ this.bridge.disconnect();
198
+ this.bridge = null;
199
+ }
200
+
201
+ if (this.storeUnsubscribe) {
202
+ this.storeUnsubscribe();
203
+ this.storeUnsubscribe = null;
204
+ }
205
+
206
+ this.unsubscribeHandlers.forEach((unsubscribe) => unsubscribe());
207
+ this.unsubscribeHandlers = [];
208
+
209
+ this.state = "loading";
210
+ this.lastSentSyncId = null;
211
+ }
212
+
213
+ private setupReadyHandler(): void {
214
+ if (!this.bridge) return;
215
+
216
+ const unsubscribe = this.bridge.onPluginMessage("ready", () => {
217
+ if (this.readyTimeout) {
218
+ clearTimeout(this.readyTimeout);
219
+ this.readyTimeout = null;
220
+ }
221
+ this.clearInitRetryInterval();
222
+
223
+ this.state = "connected";
224
+
225
+ if (this.bridge) {
226
+ this.healthCheck = new PluginHealthCheck(this.bridge, {
227
+ onUnhealthy: () => {
228
+ this.handleError(new Error("Plugin iframe is unresponsive"));
229
+ },
230
+ logger: this.logger,
231
+ });
232
+ this.healthCheck.start();
233
+ }
234
+
235
+ this.config.onReady();
236
+ });
237
+
238
+ this.unsubscribeHandlers.push(unsubscribe);
239
+ }
240
+
241
+ private sendInit(): void {
242
+ if (!this.bridge) {
243
+ return;
244
+ }
245
+
246
+ this.bridge.sendInit(
247
+ this.config.sessionId,
248
+ this.config.controllablePlayerIds,
249
+ this.config.controllingPlayerId,
250
+ this.config.userId,
251
+ );
252
+ }
253
+
254
+ private clearInitRetryInterval(): void {
255
+ if (this.initRetryInterval) {
256
+ clearInterval(this.initRetryInterval);
257
+ this.initRetryInterval = null;
258
+ }
259
+ }
260
+
261
+ private setupInteractionHandler(): void {
262
+ if (!this.bridge) return;
263
+
264
+ const unsubscribe = this.bridge.onPluginMessage(
265
+ "interaction",
266
+ async (message) => {
267
+ // Tier-0 perf: record plugin-supplied `t0_click` and our own
268
+ // `t1_host_received` against the client-minted actionId so the
269
+ // rest of the pipeline (http submit, SSE, store apply) can
270
+ // attach their marks to the same entry.
271
+ if (message.clientActionId) {
272
+ if (typeof message.clientSubmittedAtMs === "number") {
273
+ recordMark(message.clientActionId, PERF_MARK_NAMES.T0_CLICK, {
274
+ timestampMs: message.clientSubmittedAtMs,
275
+ extra: { source: "plugin" },
276
+ });
277
+ }
278
+ recordMark(message.clientActionId, PERF_MARK_NAMES.T1_HOST_RECEIVED, {
279
+ extra: { messageId: message.messageId },
280
+ });
281
+ }
282
+
283
+ try {
284
+ await this.config.onInteraction(
285
+ message.playerId,
286
+ message.interactionId,
287
+ message.params,
288
+ message.clientActionId
289
+ ? { clientActionId: message.clientActionId }
290
+ : undefined,
291
+ );
292
+ this.sendSubmitResult(message.messageId, { accepted: true });
293
+ } catch (error) {
294
+ this.logger.error("[Gateway] Interaction submission error:", error);
295
+ this.sendSubmitResult(
296
+ message.messageId,
297
+ this.describeSubmissionFailure(error, "Interaction rejected"),
298
+ );
299
+ }
300
+ },
301
+ );
302
+
303
+ this.unsubscribeHandlers.push(unsubscribe);
304
+ }
305
+
306
+ private setupValidateInteractionHandler(): void {
307
+ if (!this.bridge) return;
308
+
309
+ const unsubscribe = this.bridge.onPluginMessage(
310
+ "validate-interaction",
311
+ async (message) => {
312
+ try {
313
+ const result = await this.config.onValidateInteraction(
314
+ message.playerId,
315
+ message.interactionId,
316
+ message.params,
317
+ );
318
+
319
+ this.bridge?.send({
320
+ type: "validate-interaction-result",
321
+ messageId: message.messageId,
322
+ result,
323
+ });
324
+ } catch (error) {
325
+ this.logger.error("[Gateway] Validate interaction error:", error);
326
+
327
+ this.bridge?.send({
328
+ type: "validate-interaction-result",
329
+ messageId: message.messageId,
330
+ result: {
331
+ valid: false,
332
+ errorCode: "validation-error",
333
+ message:
334
+ error instanceof Error ? error.message : "Validation failed",
335
+ },
336
+ });
337
+ }
338
+ },
339
+ );
340
+
341
+ this.unsubscribeHandlers.push(unsubscribe);
342
+ }
343
+
344
+ private setupErrorHandler(): void {
345
+ if (!this.bridge) return;
346
+
347
+ const unsubscribe = this.bridge.onPluginMessage("error", (message) => {
348
+ this.logger.error("[Gateway] Plugin error:", message.message);
349
+ if (message.code) {
350
+ this.logger.error("[Gateway] Error code:", message.code);
351
+ }
352
+ const error = new Error(
353
+ message.code ? `${message.code}: ${message.message}` : message.message,
354
+ );
355
+ error.name = "PluginRuntimeError";
356
+ this.handleError(error);
357
+ });
358
+
359
+ this.unsubscribeHandlers.push(unsubscribe);
360
+ }
361
+
362
+ private setupSwitchPlayerHandler(): void {
363
+ if (!this.bridge) return;
364
+
365
+ const unsubscribe = this.bridge.onPluginMessage(
366
+ "switch-player",
367
+ (message) => {
368
+ this.logger.log("[Gateway] Switch player request:", message.playerId);
369
+ if (this.config.onSwitchPlayer) {
370
+ this.config.onSwitchPlayer(message.playerId);
371
+ }
372
+ },
373
+ );
374
+
375
+ this.unsubscribeHandlers.push(unsubscribe);
376
+ }
377
+
378
+ private setupRestoreHistoryHandler(): void {
379
+ if (!this.bridge) return;
380
+
381
+ const unsubscribe = this.bridge.onPluginMessage(
382
+ "restore-history",
383
+ (message) => {
384
+ this.logger.log("[Gateway] Restore history request:", message.entryId);
385
+ if (this.config.onRestoreHistory) {
386
+ this.config.onRestoreHistory(message.entryId);
387
+ }
388
+ },
389
+ );
390
+
391
+ this.unsubscribeHandlers.push(unsubscribe);
392
+ }
393
+
394
+ private setupStateAckHandler(): void {
395
+ if (!this.bridge) return;
396
+
397
+ const unsubscribe = this.bridge.onPluginMessage("state-ack", (message) => {
398
+ this.logger.log("[Gateway] Received state-ack, syncId:", message.syncId);
399
+ // Tier-0 perf: mark `t7_state_sync_received` using the plugin's
400
+ // own `Date.now()` captured at state-sync receipt. Uses the
401
+ // syncId -> actionId map populated at t5.
402
+ if (typeof message.clientReceivedAtMs === "number") {
403
+ const actionId = findActionIdBySyncId(message.syncId);
404
+ if (actionId) {
405
+ recordMark(actionId, PERF_MARK_NAMES.T7_STATE_SYNC_RECEIVED, {
406
+ timestampMs: message.clientReceivedAtMs,
407
+ extra: { syncId: message.syncId, source: "plugin" },
408
+ });
409
+ }
410
+ }
411
+ this.gameSessionStore?.onStateAck(message.syncId);
412
+ });
413
+
414
+ this.unsubscribeHandlers.push(unsubscribe);
415
+ }
416
+
417
+ private setupStateRenderedHandler(): void {
418
+ if (!this.bridge) return;
419
+
420
+ const unsubscribe = this.bridge.onPluginMessage(
421
+ "state-rendered",
422
+ (message) => {
423
+ // Tier-0 perf only: the plugin sends this after its post-sync
424
+ // microtask/rAF completes so the host HUD can show t8 - t0
425
+ // ("total" click-to-render). No functional side effects.
426
+ const actionId = findActionIdBySyncId(message.syncId);
427
+ if (!actionId) return;
428
+ recordMark(actionId, PERF_MARK_NAMES.T7_STATE_SYNC_RECEIVED, {
429
+ timestampMs: message.clientReceivedAtMs,
430
+ extra: { syncId: message.syncId, source: "plugin" },
431
+ });
432
+ recordMark(actionId, PERF_MARK_NAMES.T8_RENDER_COMMIT, {
433
+ timestampMs: message.clientRenderedAtMs,
434
+ extra: { syncId: message.syncId, source: "plugin" },
435
+ });
436
+ },
437
+ );
438
+
439
+ this.unsubscribeHandlers.push(unsubscribe);
440
+ }
441
+
442
+ private setupMarkNotificationReadHandler(): void {
443
+ if (!this.bridge) return;
444
+
445
+ const unsubscribe = this.bridge.onPluginMessage(
446
+ "mark-notification-read",
447
+ (message) => {
448
+ this.logger.log(
449
+ "[Gateway] Mark notification read:",
450
+ message.notificationId,
451
+ );
452
+ this.gameSessionStore?.markNotificationRead(message.notificationId);
453
+ },
454
+ );
455
+
456
+ this.unsubscribeHandlers.push(unsubscribe);
457
+ }
458
+
459
+ private handleError(error: Error): void {
460
+ if (this.state === "error") {
461
+ return;
462
+ }
463
+ if (this.readyTimeout) {
464
+ clearTimeout(this.readyTimeout);
465
+ this.readyTimeout = null;
466
+ }
467
+ if (this.initStartTimeout) {
468
+ clearTimeout(this.initStartTimeout);
469
+ this.initStartTimeout = null;
470
+ }
471
+ this.clearInitRetryInterval();
472
+ if (this.healthCheck) {
473
+ this.healthCheck.stop();
474
+ this.healthCheck = null;
475
+ }
476
+ this.logger.error("[Gateway] Error:", error);
477
+ this.state = "error";
478
+ this.config.onError(error);
479
+ }
480
+
481
+ private sendSubmitResult(
482
+ messageId: string,
483
+ result: {
484
+ accepted: boolean;
485
+ errorCode?: string;
486
+ message?: string;
487
+ },
488
+ ): void {
489
+ this.bridge?.sendSubmitResult({
490
+ messageId,
491
+ accepted: result.accepted,
492
+ errorCode: result.errorCode,
493
+ message: result.message,
494
+ });
495
+ }
496
+
497
+ private describeSubmissionFailure(
498
+ error: unknown,
499
+ fallbackMessage: string,
500
+ ): {
501
+ accepted: false;
502
+ errorCode?: string;
503
+ message: string;
504
+ } {
505
+ const errorCode =
506
+ typeof error === "object" &&
507
+ error !== null &&
508
+ "errorCode" in error &&
509
+ typeof error.errorCode === "string"
510
+ ? error.errorCode
511
+ : undefined;
512
+ const message =
513
+ error instanceof Error
514
+ ? error.message
515
+ : typeof error === "string"
516
+ ? error
517
+ : fallbackMessage;
518
+ return {
519
+ accepted: false,
520
+ errorCode,
521
+ message,
522
+ };
523
+ }
524
+ }
@@ -0,0 +1,12 @@
1
+ export * from "../actor-principal.js";
2
+ export * from "../logger.js";
3
+ export * from "../host-session-transport.js";
4
+ export * from "../perf.js";
5
+ export * from "../plugin-bridge.js";
6
+ export * from "../plugin-health-check.js";
7
+ export * from "../plugin-messages.js";
8
+ export * from "../plugin-session-gateway.js";
9
+ export * from "../screenshot/projection-to-snapshot.js";
10
+ export * from "../screenshot/static-store-api.js";
11
+ export * from "../sse-manager.js";
12
+ export * from "../unified-session-store.js";
@@ -0,0 +1,122 @@
1
+ import type { SeatAssignment } from "@dreamboard/api-client";
2
+ import type { PluginStateSnapshot } from "@dreamboard/ui-sdk/reducer";
3
+ import { seatsForPluginSnapshot } from "../actor-principal.js";
4
+
5
+ const DEFAULT_PLAYER_IDS = [
6
+ "player-1",
7
+ "player-2",
8
+ "player-3",
9
+ "player-4",
10
+ ] as const;
11
+
12
+ const PLAYER_COLORS = [
13
+ "#f97316",
14
+ "#0ea5e9",
15
+ "#22c55e",
16
+ "#a855f7",
17
+ "#ef4444",
18
+ "#eab308",
19
+ ] as const;
20
+
21
+ export interface ScreenshotProjection {
22
+ currentPhase?: string | null;
23
+ currentStage?: string | null;
24
+ stageSeats?: string[];
25
+ simultaneousPhase?: PluginStateSnapshot["gameplay"]["simultaneousPhase"];
26
+ view?: unknown;
27
+ availableInteractions?: PluginStateSnapshot["gameplay"]["availableInteractions"];
28
+ zones?: PluginStateSnapshot["gameplay"]["zones"];
29
+ }
30
+
31
+ export interface ProjectionToSnapshotOptions {
32
+ sessionId?: string | null;
33
+ userId?: string | null;
34
+ controllingPlayerId?: string;
35
+ playerIds?: string[];
36
+ syncId?: number;
37
+ }
38
+
39
+ export function projectionToSnapshot(
40
+ projection: ScreenshotProjection,
41
+ options: ProjectionToSnapshotOptions = {},
42
+ ): PluginStateSnapshot {
43
+ const controllingPlayerId =
44
+ options.controllingPlayerId ??
45
+ projection.stageSeats?.[0] ??
46
+ DEFAULT_PLAYER_IDS[0];
47
+ const playerIds = normalizePlayerIds(options.playerIds, controllingPlayerId);
48
+ const userId = options.userId ?? "screenshot-user";
49
+ const seats = buildSeatAssignments(playerIds, userId);
50
+
51
+ return {
52
+ view: (projection.view ?? null) as PluginStateSnapshot["view"],
53
+ gameplay: {
54
+ currentPhase:
55
+ projection.currentPhase ??
56
+ viewCurrentPhase(projection.view) ??
57
+ projection.currentStage ??
58
+ null,
59
+ currentStage: projection.currentStage ?? null,
60
+ activePlayers:
61
+ projection.stageSeats && projection.stageSeats.length > 0
62
+ ? projection.stageSeats
63
+ : [controllingPlayerId],
64
+ simultaneousPhase: projection.simultaneousPhase ?? null,
65
+ availableInteractions: projection.availableInteractions ?? [],
66
+ zones: projection.zones ?? {},
67
+ },
68
+ lobby: {
69
+ seats: seatsForPluginSnapshot(seats),
70
+ canStart: true,
71
+ hostUserId: userId,
72
+ },
73
+ notifications: [],
74
+ session: {
75
+ sessionId: options.sessionId ?? null,
76
+ controllablePlayerIds: [controllingPlayerId],
77
+ controllingPlayerId,
78
+ userId,
79
+ },
80
+ history: null,
81
+ syncId: options.syncId ?? 1,
82
+ };
83
+ }
84
+
85
+ function viewCurrentPhase(view: unknown): string | null {
86
+ if (!view || typeof view !== "object" || !("currentPhase" in view)) {
87
+ return null;
88
+ }
89
+ const value = (view as { currentPhase?: unknown }).currentPhase;
90
+ return typeof value === "string" && value.length > 0 ? value : null;
91
+ }
92
+
93
+ function normalizePlayerIds(
94
+ playerIds: string[] | undefined,
95
+ controllingPlayerId: string,
96
+ ): string[] {
97
+ const normalized = (playerIds?.length ? playerIds : [...DEFAULT_PLAYER_IDS])
98
+ .map((playerId) => playerId.trim())
99
+ .filter(Boolean);
100
+ return Array.from(new Set([controllingPlayerId, ...normalized]));
101
+ }
102
+
103
+ function buildSeatAssignments(
104
+ playerIds: string[],
105
+ userId: string,
106
+ ): SeatAssignment[] {
107
+ return playerIds.map((playerId, index) => ({
108
+ playerId,
109
+ controllerActor: {
110
+ kind: "DEMO_GUEST",
111
+ demoActorSessionId: userId,
112
+ },
113
+ displayName: displayNameForPlayer(playerId),
114
+ playerColor: PLAYER_COLORS[index % PLAYER_COLORS.length],
115
+ isHost: index === 0,
116
+ }));
117
+ }
118
+
119
+ function displayNameForPlayer(playerId: string): string {
120
+ const match = /^player-(\d+)$/.exec(playerId);
121
+ return match ? `Player ${match[1]}` : playerId;
122
+ }
@@ -0,0 +1,26 @@
1
+ import type { PluginStateSnapshot } from "@dreamboard/ui-sdk/reducer";
2
+ import type { GameSessionStoreApi } from "../plugin-session-gateway.js";
3
+
4
+ export function createStaticStoreApi(
5
+ snapshot: PluginStateSnapshot,
6
+ ): GameSessionStoreApi {
7
+ const frozenSnapshot = deepFreeze(snapshot);
8
+ return {
9
+ getStateSnapshot: () => frozenSnapshot,
10
+ subscribe: () => () => undefined,
11
+ onStateAck: () => undefined,
12
+ markNotificationRead: () => undefined,
13
+ };
14
+ }
15
+
16
+ function deepFreeze<T>(value: T): T {
17
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) {
18
+ return value;
19
+ }
20
+
21
+ for (const nested of Object.values(value as Record<string, unknown>)) {
22
+ deepFreeze(nested);
23
+ }
24
+
25
+ return Object.freeze(value);
26
+ }