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,477 @@
1
+ import type { StoreApi } from "zustand/vanilla";
2
+ import type { LoggerLike } from "./logger.js";
3
+ import {
4
+ PERF_MARK_NAMES,
5
+ correlateVersion,
6
+ correlateSyncId,
7
+ findActionIdByVersion,
8
+ recordMark,
9
+ } from "./perf.js";
10
+ import type { HostSessionTransport } from "./host-session-transport.js";
11
+ import type {
12
+ HostActionSubmitWireResponse,
13
+ NormalizedSubmitResponse,
14
+ } from "./session-ingress.js";
15
+ import {
16
+ normalizeEvent,
17
+ normalizeSnapshot,
18
+ normalizeSubmitResponse,
19
+ } from "./session-ingress.js";
20
+ import {
21
+ getGameplayViewport,
22
+ getSessionContext,
23
+ resolveControllablePlayerIds,
24
+ } from "./session-model.js";
25
+ import type { SSEManagerLike } from "./session-live-runtime.js";
26
+ import { createSessionLiveRuntime } from "./session-live-runtime.js";
27
+ import {
28
+ createInitialUnifiedSessionState,
29
+ reduceSessionState,
30
+ type SessionStateEffect,
31
+ type SessionStateIngress,
32
+ type SessionStateReducerEnvironment,
33
+ type UnifiedSessionState,
34
+ } from "./session-state-reducer.js";
35
+
36
+ export interface SessionIngressControllerActions {
37
+ loadSessionByShortCode: (input: {
38
+ shortCode: string;
39
+ userId?: string | null;
40
+ requestedPlayerId?: string | null;
41
+ source?: string;
42
+ }) => Promise<void>;
43
+ loadSessionSnapshot: (input: {
44
+ sessionId: string;
45
+ userId?: string | null;
46
+ requestedPlayerId?: string | null;
47
+ expectedPerspectivePlayerId?: string | null;
48
+ source?: string;
49
+ }) => Promise<void>;
50
+ startSession: (input: {
51
+ sessionId: string;
52
+ userId?: string | null;
53
+ source?: string;
54
+ }) => Promise<void>;
55
+ submitInteraction: (input: {
56
+ sessionId: string;
57
+ playerId: string;
58
+ interactionId: string;
59
+ params: unknown;
60
+ clientActionId?: string | null;
61
+ }) => Promise<void>;
62
+ restoreHistory: (input: {
63
+ sessionId: string;
64
+ entryId: string;
65
+ }) => Promise<void>;
66
+ selectPlayer: (playerId: string) => void;
67
+ clearConnectionError: () => void;
68
+ enqueueActionRejected: (reason: string, targetPlayer?: string) => void;
69
+ closeStreams: () => void;
70
+ onStateAck: (syncId: number) => void;
71
+ markNotificationRead: (id: string) => void;
72
+ clearNotifications: () => void;
73
+ dismissHostFeedback: (id: string) => void;
74
+ clearHostFeedback: () => void;
75
+ clearSSEEvents: () => void;
76
+ reset: () => void;
77
+ }
78
+
79
+ export interface CreateSessionIngressControllerOptions<
80
+ TStore extends UnifiedSessionState,
81
+ > {
82
+ store: StoreApi<TStore>;
83
+ createSseManager: () => SSEManagerLike;
84
+ transport: HostSessionTransport;
85
+ logger: LoggerLike;
86
+ fallbackToAllSeatsWhenUserIdMissing: boolean;
87
+ reducerEnvironment: SessionStateReducerEnvironment;
88
+ }
89
+
90
+ function describeSseError(error: unknown): string {
91
+ if (error instanceof Error) {
92
+ if (
93
+ error.message.includes("429") ||
94
+ error.message.includes("400 Bad Request")
95
+ ) {
96
+ return "Too many live views are open for this session. Close unused tabs or devices, then refresh.";
97
+ }
98
+ return "Live session updates disconnected. Check your network and refresh to reconnect.";
99
+ }
100
+ return "Live session updates disconnected. Refresh to reconnect.";
101
+ }
102
+
103
+ function describeCommandFailure(error: unknown, fallback: string): string {
104
+ if (error instanceof Error && error.message.trim()) {
105
+ return error.message;
106
+ }
107
+ const payload = error as { detail?: unknown; message?: unknown } | null;
108
+ if (typeof payload?.detail === "string" && payload.detail.trim()) {
109
+ return payload.detail;
110
+ }
111
+ if (typeof payload?.message === "string" && payload.message.trim()) {
112
+ return payload.message;
113
+ }
114
+ return fallback;
115
+ }
116
+
117
+ function createSubmissionError(
118
+ errorCode: string | undefined,
119
+ message: string | undefined,
120
+ ): Error & { errorCode?: string } {
121
+ const error = new Error(message ?? "Interaction rejected") as Error & {
122
+ errorCode?: string;
123
+ };
124
+ error.name = "SubmissionError";
125
+ error.errorCode = errorCode;
126
+ return error;
127
+ }
128
+
129
+ export function createSessionIngressController<
130
+ TStore extends UnifiedSessionState,
131
+ >(
132
+ options: CreateSessionIngressControllerOptions<TStore>,
133
+ ): SessionIngressControllerActions {
134
+ const {
135
+ store,
136
+ transport,
137
+ logger,
138
+ fallbackToAllSeatsWhenUserIdMissing,
139
+ reducerEnvironment,
140
+ } = options;
141
+
142
+ const dispatchIngress = (ingress: SessionStateIngress) => {
143
+ const { state, effects } = reduceSessionState(
144
+ store.getState(),
145
+ ingress,
146
+ reducerEnvironment,
147
+ );
148
+ store.setState(state as Partial<TStore>);
149
+ effects.forEach(executeEffect);
150
+ };
151
+
152
+ const runtime = createSessionLiveRuntime({
153
+ createSseManager: options.createSseManager,
154
+ onConnectionChange: (channel, connected) => {
155
+ dispatchIngress({ type: "connection.changed", channel, connected });
156
+ },
157
+ onConnectionError: (error) => {
158
+ logger.error("[UnifiedSession] SSE connection error:", error);
159
+ dispatchIngress({
160
+ type: "connection.failed",
161
+ message: describeSseError(error),
162
+ });
163
+ },
164
+ onLiveMessage: (message) => {
165
+ const state = store.getState();
166
+ const context = getSessionContext(state.session);
167
+ const event = normalizeEvent(message, {
168
+ currentContext: context,
169
+ currentGameplay: getGameplayViewport(state.session),
170
+ userId: context?.userId ?? null,
171
+ });
172
+ dispatchIngress({
173
+ type: "event.received",
174
+ source: "sse",
175
+ event,
176
+ debugEvent: { eventType: message.type, data: message },
177
+ });
178
+ },
179
+ });
180
+
181
+ function executeEffect(effect: SessionStateEffect): void {
182
+ switch (effect.type) {
183
+ case "requestGameplayResync":
184
+ logger.warn(
185
+ `[UnifiedSession] ${effect.reason}; reconnecting to refresh gameplay.bootstrap`,
186
+ );
187
+ runtime.requestGameplayResync(
188
+ effect.sessionId,
189
+ effect.playerId,
190
+ "board-static-resync",
191
+ );
192
+ break;
193
+ case "reconnectGameplay":
194
+ runtime.reconnectGameplay(
195
+ effect.sessionId,
196
+ effect.playerId,
197
+ effect.source,
198
+ );
199
+ break;
200
+ case "perf.storeApplied": {
201
+ const actionId = findActionIdByVersion(effect.gameplayVersion);
202
+ if (!actionId) break;
203
+ recordMark(actionId, PERF_MARK_NAMES.T5_STORE_APPLIED, {
204
+ extra: {
205
+ version: effect.gameplayVersion,
206
+ syncId: effect.syncId,
207
+ },
208
+ });
209
+ correlateSyncId(actionId, effect.syncId);
210
+ break;
211
+ }
212
+ case "log.warn":
213
+ logger.warn(effect.message);
214
+ break;
215
+ }
216
+ }
217
+
218
+ function connectLiveSession(
219
+ sessionId: string,
220
+ userId: string | null,
221
+ connectOptions: { source?: string; playerId?: string } = {},
222
+ ) {
223
+ const context = getSessionContext(store.getState().session);
224
+ const controllablePlayerIds = context
225
+ ? resolveControllablePlayerIds(
226
+ context.switchablePlayerIds,
227
+ context.seats,
228
+ userId,
229
+ fallbackToAllSeatsWhenUserIdMissing,
230
+ )
231
+ : [];
232
+ const session = store.getState().session;
233
+ const playerId =
234
+ connectOptions.playerId ??
235
+ (session.type === "gameplay"
236
+ ? session.perspective.playerId
237
+ : controllablePlayerIds[0]);
238
+ dispatchIngress({
239
+ type: "connection.prepared",
240
+ userId,
241
+ switchablePlayerIds: controllablePlayerIds,
242
+ });
243
+ logger.log(`[UnifiedSession] Connecting to session: ${sessionId}`);
244
+ runtime.connectSession(sessionId, {
245
+ source: connectOptions.source,
246
+ playerId,
247
+ });
248
+ }
249
+
250
+ async function runSnapshotCommand(
251
+ input: {
252
+ target?: { sessionId?: string; shortCode?: string };
253
+ sourceLabel: "short-code" | "snapshot" | "start" | "dev-new";
254
+ connectSource?: string;
255
+ userId?: string | null;
256
+ expectedPerspectivePlayerId?: string | null;
257
+ preserveCurrentOnFailure?: boolean;
258
+ load: () => Promise<
259
+ Awaited<ReturnType<HostSessionTransport["loadSessionSnapshot"]>>
260
+ >;
261
+ },
262
+ failureMessage: string,
263
+ ): Promise<void> {
264
+ if (!input.preserveCurrentOnFailure) {
265
+ dispatchIngress({ type: "command.loading", target: input.target });
266
+ }
267
+ try {
268
+ const snapshot = await input.load();
269
+ const normalized = normalizeSnapshot(snapshot, {
270
+ userId: input.userId ?? null,
271
+ previousGameplay: getGameplayViewport(store.getState().session),
272
+ });
273
+ dispatchIngress({
274
+ type: "snapshot.loaded",
275
+ source: input.sourceLabel,
276
+ snapshot: normalized,
277
+ expectedPerspectivePlayerId: input.expectedPerspectivePlayerId,
278
+ });
279
+ connectLiveSession(
280
+ normalized.context.identity.sessionId,
281
+ input.userId ?? null,
282
+ {
283
+ source: input.connectSource,
284
+ playerId:
285
+ normalized.type === "gameplay"
286
+ ? normalized.perspective.playerId
287
+ : undefined,
288
+ },
289
+ );
290
+ } catch (error) {
291
+ if (!input.preserveCurrentOnFailure) {
292
+ dispatchIngress({
293
+ type: "command.failed",
294
+ message: describeCommandFailure(error, failureMessage),
295
+ });
296
+ }
297
+ throw error;
298
+ }
299
+ }
300
+
301
+ function normalizeSubmitResponseForCurrentState(
302
+ response: HostActionSubmitWireResponse,
303
+ ): NormalizedSubmitResponse {
304
+ const state = store.getState();
305
+ const context = getSessionContext(state.session);
306
+ return normalizeSubmitResponse(response, {
307
+ currentContext: context,
308
+ currentGameplay: getGameplayViewport(state.session),
309
+ userId: context?.userId ?? null,
310
+ });
311
+ }
312
+
313
+ function dispatchSubmitResponse(
314
+ event: NormalizedSubmitResponse,
315
+ clientActionId?: string | null,
316
+ ) {
317
+ if (!event) return;
318
+ if (clientActionId && event.type === "session.gameplayUpdated") {
319
+ recordMark(clientActionId, PERF_MARK_NAMES.T3B_RESPONSE_APPLIED, {
320
+ extra: { version: event.gameplay.version },
321
+ });
322
+ }
323
+ dispatchIngress({
324
+ type: "event.received",
325
+ source: "submit-response",
326
+ event,
327
+ clientActionId,
328
+ });
329
+ }
330
+
331
+ return {
332
+ loadSessionByShortCode: async (input) => {
333
+ await runSnapshotCommand(
334
+ {
335
+ target: { shortCode: input.shortCode },
336
+ sourceLabel: "short-code",
337
+ connectSource: input.source,
338
+ userId: input.userId,
339
+ load: () =>
340
+ transport.loadSessionByShortCode({
341
+ shortCode: input.shortCode,
342
+ requestedPlayerId: input.requestedPlayerId,
343
+ }),
344
+ },
345
+ "Failed to load session by short code.",
346
+ );
347
+ },
348
+ loadSessionSnapshot: async (input) => {
349
+ await runSnapshotCommand(
350
+ {
351
+ target: { sessionId: input.sessionId },
352
+ sourceLabel: input.source === "dev-new" ? "dev-new" : "snapshot",
353
+ connectSource: input.source,
354
+ userId: input.userId,
355
+ expectedPerspectivePlayerId: input.expectedPerspectivePlayerId,
356
+ preserveCurrentOnFailure: input.source === "player-switch",
357
+ load: () =>
358
+ transport.loadSessionSnapshot({
359
+ sessionId: input.sessionId,
360
+ requestedPlayerId: input.requestedPlayerId,
361
+ }),
362
+ },
363
+ "Failed to load session snapshot.",
364
+ );
365
+ },
366
+ startSession: async (input) => {
367
+ await runSnapshotCommand(
368
+ {
369
+ target: { sessionId: input.sessionId },
370
+ sourceLabel: "start",
371
+ connectSource: input.source,
372
+ userId: input.userId,
373
+ load: () => transport.startSession({ sessionId: input.sessionId }),
374
+ },
375
+ "Failed to start session.",
376
+ );
377
+ },
378
+ submitInteraction: async (input) => {
379
+ const gameplay = getGameplayViewport(store.getState().session);
380
+ if (!gameplay) {
381
+ throw new Error("No renderable gameplay snapshot is available.");
382
+ }
383
+ if (input.clientActionId) {
384
+ recordMark(input.clientActionId, PERF_MARK_NAMES.T2_HTTP_SENT, {
385
+ extra: {
386
+ playerId: input.playerId,
387
+ interactionId: input.interactionId,
388
+ expectedVersion: gameplay.version,
389
+ },
390
+ });
391
+ }
392
+ const response = await transport.submitInteraction({
393
+ sessionId: input.sessionId,
394
+ playerId: input.playerId,
395
+ interactionId: input.interactionId,
396
+ expectedVersion: gameplay.version,
397
+ actionSetVersion: gameplay.actionSetVersion,
398
+ params: input.params,
399
+ clientActionId: input.clientActionId,
400
+ });
401
+ const normalizedResponse =
402
+ normalizeSubmitResponseForCurrentState(response);
403
+ if (input.clientActionId) {
404
+ recordMark(input.clientActionId, PERF_MARK_NAMES.T3_HTTP_RESPONSE, {
405
+ extra: {
406
+ accepted: response.accepted,
407
+ errorCode: response.errorCode,
408
+ version: response.version,
409
+ transport: "ok",
410
+ },
411
+ });
412
+ const acceptedGameplayVersion =
413
+ normalizedResponse?.type === "session.gameplayUpdated"
414
+ ? normalizedResponse.gameplay.version
415
+ : response.version;
416
+ if (
417
+ acceptedGameplayVersion !== undefined &&
418
+ response.accepted !== false
419
+ ) {
420
+ correlateVersion(input.clientActionId, acceptedGameplayVersion);
421
+ }
422
+ }
423
+ if (response.accepted !== false) {
424
+ dispatchSubmitResponse(normalizedResponse, input.clientActionId);
425
+ return;
426
+ }
427
+ dispatchIngress({
428
+ type: "feedback.actionRejected",
429
+ reason: response.message ?? "Interaction rejected",
430
+ targetPlayer: input.playerId,
431
+ });
432
+ throw createSubmissionError(
433
+ response.errorCode ?? undefined,
434
+ response.message ?? "Interaction rejected",
435
+ );
436
+ },
437
+ restoreHistory: async (input) => {
438
+ await transport.restoreHistory(input);
439
+ },
440
+ selectPlayer: (playerId) => {
441
+ const context = getSessionContext(store.getState().session);
442
+ if (!context) return;
443
+ dispatchIngress({
444
+ type: "local.playerSelected",
445
+ playerId,
446
+ sessionId:
447
+ runtime.getConnectedSessionId() ?? context.identity.sessionId,
448
+ });
449
+ },
450
+ clearConnectionError: () =>
451
+ dispatchIngress({ type: "connection.errorCleared" }),
452
+ enqueueActionRejected: (reason, targetPlayer) =>
453
+ dispatchIngress({
454
+ type: "feedback.actionRejected",
455
+ reason,
456
+ targetPlayer,
457
+ }),
458
+ closeStreams: () => {
459
+ runtime.closeStreams();
460
+ dispatchIngress({ type: "streams.closed" });
461
+ },
462
+ onStateAck: () => dispatchIngress({ type: "activity.stateAcked" }),
463
+ markNotificationRead: (id) =>
464
+ dispatchIngress({ type: "activity.notificationRead", id }),
465
+ clearNotifications: () =>
466
+ dispatchIngress({ type: "activity.notificationsCleared" }),
467
+ dismissHostFeedback: (id) =>
468
+ dispatchIngress({ type: "activity.hostFeedbackDismissed", id }),
469
+ clearHostFeedback: () =>
470
+ dispatchIngress({ type: "activity.hostFeedbackCleared" }),
471
+ clearSSEEvents: () => dispatchIngress({ type: "debug.sseEventsCleared" }),
472
+ reset: () => {
473
+ runtime.closeStreams();
474
+ store.setState(createInitialUnifiedSessionState() as Partial<TStore>);
475
+ },
476
+ };
477
+ }
@@ -0,0 +1,209 @@
1
+ import type {
2
+ HostActionSubmitResponse,
3
+ HostSessionEvent,
4
+ HostSessionSnapshot,
5
+ } from "@dreamboard/api-client";
6
+ import type {
7
+ GameplayPerspective,
8
+ GameplayViewport,
9
+ SessionContext,
10
+ } from "./session-model.js";
11
+ import {
12
+ contextFromHostEventContext,
13
+ contextFromHostSnapshot,
14
+ gameplayViewportFromSnapshot,
15
+ } from "./session-model.js";
16
+
17
+ type WireSessionIngressSnapshot = HostSessionSnapshot;
18
+ type WireSessionIngressEvent = HostSessionEvent;
19
+ type WireSessionIngressActionSubmitResponse = HostActionSubmitResponse;
20
+
21
+ export type HostSessionWireSnapshot = WireSessionIngressSnapshot;
22
+ export type HostSessionWireEvent = WireSessionIngressEvent;
23
+ export type HostActionSubmitWireResponse =
24
+ WireSessionIngressActionSubmitResponse;
25
+
26
+ export type NormalizedSessionSnapshot =
27
+ | {
28
+ type: "lobby";
29
+ context: SessionContext;
30
+ gameplay: null;
31
+ preferredPlayerId: string | null;
32
+ }
33
+ | {
34
+ type: "gameplay";
35
+ context: SessionContext;
36
+ perspective: GameplayPerspective;
37
+ gameplay: GameplayViewport;
38
+ }
39
+ | {
40
+ type: "ended";
41
+ context: SessionContext;
42
+ gameplay: null;
43
+ preferredPlayerId: string | null;
44
+ };
45
+
46
+ export type NormalizedSessionEvent =
47
+ | {
48
+ type: "session.snapshot";
49
+ snapshot: NormalizedSessionSnapshot;
50
+ context: SessionContext;
51
+ gameplay: GameplayViewport | null;
52
+ }
53
+ | {
54
+ type: "session.lobbyUpdated";
55
+ context: SessionContext | null;
56
+ gameplay: GameplayViewport | null;
57
+ }
58
+ | {
59
+ type: "session.gameplayUpdated";
60
+ context: SessionContext | null;
61
+ gameplay: GameplayViewport;
62
+ perspective: GameplayPerspective;
63
+ }
64
+ | {
65
+ type: "session.historyUpdated";
66
+ context: SessionContext | null;
67
+ gameplay: GameplayViewport | null;
68
+ }
69
+ | {
70
+ type: "session.ended";
71
+ context: SessionContext | null;
72
+ gameplay: GameplayViewport | null;
73
+ }
74
+ | {
75
+ type: "session.error";
76
+ message: string;
77
+ code?: string;
78
+ context: SessionContext | null;
79
+ gameplay: GameplayViewport | null;
80
+ };
81
+
82
+ export type NormalizedSubmitResponse = NormalizedSessionEvent | null;
83
+
84
+ export function normalizeSnapshot(
85
+ snapshot: HostSessionWireSnapshot,
86
+ options: {
87
+ userId: string | null;
88
+ previousGameplay?: GameplayViewport | null;
89
+ },
90
+ ): NormalizedSessionSnapshot {
91
+ const context = contextFromHostSnapshot(snapshot, options.userId);
92
+ if (snapshot.type !== "gameplay") {
93
+ return {
94
+ type: snapshot.type,
95
+ context,
96
+ gameplay: null,
97
+ preferredPlayerId: null,
98
+ };
99
+ }
100
+ return {
101
+ type: "gameplay",
102
+ context,
103
+ perspective: { playerId: snapshot.gameplay.playerId },
104
+ gameplay: gameplayViewportFromSnapshot(
105
+ snapshot.gameplay,
106
+ options.previousGameplay,
107
+ ),
108
+ };
109
+ }
110
+
111
+ export function normalizeEvent(
112
+ event: HostSessionWireEvent,
113
+ options: {
114
+ currentContext: SessionContext | null;
115
+ currentGameplay: GameplayViewport | null;
116
+ userId: string | null;
117
+ },
118
+ ): NormalizedSessionEvent {
119
+ const context = options.currentContext;
120
+ switch (event.type) {
121
+ case "session.snapshot": {
122
+ const normalized = normalizeSnapshot(event.snapshot, {
123
+ userId: options.userId,
124
+ previousGameplay: options.currentGameplay,
125
+ });
126
+ return {
127
+ type: "session.snapshot",
128
+ snapshot: normalized,
129
+ context: normalized.context,
130
+ gameplay: normalized.gameplay,
131
+ };
132
+ }
133
+ case "session.lobbyUpdated":
134
+ return {
135
+ type: "session.lobbyUpdated",
136
+ context: context
137
+ ? contextFromHostEventContext(
138
+ { ...context, userId: options.userId },
139
+ event.context,
140
+ event.lobby,
141
+ )
142
+ : null,
143
+ gameplay: options.currentGameplay,
144
+ };
145
+ case "session.gameplayUpdated":
146
+ return {
147
+ type: "session.gameplayUpdated",
148
+ context: context
149
+ ? contextFromHostEventContext(
150
+ { ...context, userId: options.userId },
151
+ event.context,
152
+ )
153
+ : null,
154
+ gameplay: gameplayViewportFromSnapshot(
155
+ event.gameplay,
156
+ options.currentGameplay,
157
+ ),
158
+ perspective: { playerId: event.gameplay.playerId },
159
+ };
160
+ case "session.historyUpdated":
161
+ return {
162
+ type: "session.historyUpdated",
163
+ context: context
164
+ ? contextFromHostEventContext(
165
+ { ...context, userId: options.userId },
166
+ event.context,
167
+ )
168
+ : null,
169
+ gameplay: options.currentGameplay,
170
+ };
171
+ case "session.ended":
172
+ return {
173
+ type: "session.ended",
174
+ context: context
175
+ ? contextFromHostEventContext(
176
+ { ...context, userId: options.userId },
177
+ event.context,
178
+ event.lobby,
179
+ )
180
+ : null,
181
+ gameplay: options.currentGameplay,
182
+ };
183
+ case "session.error":
184
+ return {
185
+ type: "session.error",
186
+ message: event.message,
187
+ code: event.code,
188
+ context,
189
+ gameplay: options.currentGameplay,
190
+ };
191
+ }
192
+ }
193
+
194
+ export function normalizeSubmitResponse(
195
+ response: HostActionSubmitWireResponse,
196
+ options: {
197
+ currentContext: SessionContext | null;
198
+ currentGameplay: GameplayViewport | null;
199
+ userId: string | null;
200
+ },
201
+ ): NormalizedSubmitResponse {
202
+ return response.event
203
+ ? normalizeEvent(response.event, {
204
+ currentContext: options.currentContext,
205
+ currentGameplay: options.currentGameplay,
206
+ userId: options.userId,
207
+ })
208
+ : null;
209
+ }