@seriousgaming/sdk 0.0.0-stage → 1.0.0-alpha.2

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 (170) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +1 -0
  3. package/README.md +33 -2
  4. package/lib/build/callbacks.d.ts +998 -0
  5. package/lib/build/callbacks.js +32 -0
  6. package/lib/build/context-merge.d.ts +59 -0
  7. package/lib/build/context-merge.js +118 -0
  8. package/lib/build/document.d.ts +36 -0
  9. package/lib/build/document.js +8 -0
  10. package/lib/build/encoding.d.ts +26 -0
  11. package/lib/build/encoding.js +78 -0
  12. package/lib/build/endpoints.d.ts +421 -0
  13. package/lib/build/endpoints.js +46 -0
  14. package/lib/build/hooks.d.ts +74 -0
  15. package/lib/build/hooks.js +1 -0
  16. package/lib/build/index.d.ts +18 -0
  17. package/lib/build/index.js +16 -0
  18. package/lib/build/llm.d.ts +166 -0
  19. package/lib/build/llm.js +17 -0
  20. package/lib/build/resource-schema.d.ts +1048 -0
  21. package/lib/build/resource-schema.js +7 -0
  22. package/lib/build/scheduling.d.ts +112 -0
  23. package/lib/build/scheduling.js +217 -0
  24. package/lib/build/seed.d.ts +51 -0
  25. package/lib/build/seed.js +89 -0
  26. package/lib/build/signatures.d.ts +372 -0
  27. package/lib/build/signatures.js +1 -0
  28. package/lib/build/speech.d.ts +102 -0
  29. package/lib/build/speech.js +11 -0
  30. package/lib/build/tick-validation.d.ts +23 -0
  31. package/lib/build/tick-validation.js +96 -0
  32. package/lib/build/timer.d.ts +74 -0
  33. package/lib/build/timer.js +1 -0
  34. package/lib/build/validators.d.ts +116 -0
  35. package/lib/build/validators.js +1 -0
  36. package/lib/commands.d.ts +357 -0
  37. package/lib/commands.js +14 -0
  38. package/lib/e2e.d.ts +238 -0
  39. package/lib/e2e.js +248 -0
  40. package/lib/projection.d.ts +266 -0
  41. package/lib/projection.js +23 -0
  42. package/lib/testing/index.d.ts +13 -0
  43. package/lib/testing/index.js +13 -0
  44. package/lib/testing/logic-args.d.ts +177 -0
  45. package/lib/testing/logic-args.js +229 -0
  46. package/lib/testing/stub-services.d.ts +41 -0
  47. package/lib/testing/stub-services.js +176 -0
  48. package/lib/ui/CrossTeamDataContext.d.ts +36 -0
  49. package/lib/ui/CrossTeamDataContext.js +32 -0
  50. package/lib/ui/PresentationContext.d.ts +72 -0
  51. package/lib/ui/PresentationContext.js +13 -0
  52. package/lib/ui/PresentationWrapper.d.ts +12 -0
  53. package/lib/ui/PresentationWrapper.js +78 -0
  54. package/lib/ui/SessionUserInterfaceCallbacksContext.d.ts +3 -0
  55. package/lib/ui/SessionUserInterfaceCallbacksContext.js +7 -0
  56. package/lib/ui/SessionUserInterfaceDataContext.d.ts +103 -0
  57. package/lib/ui/SessionUserInterfaceDataContext.js +17 -0
  58. package/lib/ui/SessionUserInterfaceHookFailuresContext.d.ts +44 -0
  59. package/lib/ui/SessionUserInterfaceHookFailuresContext.js +15 -0
  60. package/lib/ui/SessionUserInterfaceHostOverlaysContext.d.ts +23 -0
  61. package/lib/ui/SessionUserInterfaceHostOverlaysContext.js +23 -0
  62. package/lib/ui/SessionUserInterfaceLocaleContext.d.ts +11 -0
  63. package/lib/ui/SessionUserInterfaceLocaleContext.js +13 -0
  64. package/lib/ui/SessionUserInterfaceMetaContext.d.ts +39 -0
  65. package/lib/ui/SessionUserInterfaceMetaContext.js +13 -0
  66. package/lib/ui/SessionUserInterfaceParticipantsContext.d.ts +12 -0
  67. package/lib/ui/SessionUserInterfaceParticipantsContext.js +16 -0
  68. package/lib/ui/SessionUserInterfaceTeamConfigurationContext.d.ts +53 -0
  69. package/lib/ui/SessionUserInterfaceTeamConfigurationContext.js +23 -0
  70. package/lib/ui/SessionUserInterfaceTeamLinksContext.d.ts +56 -0
  71. package/lib/ui/SessionUserInterfaceTeamLinksContext.js +12 -0
  72. package/lib/ui/SessionUserInterfaceTeamsContext.d.ts +13 -0
  73. package/lib/ui/SessionUserInterfaceTeamsContext.js +13 -0
  74. package/lib/ui/SessionUserInterfaceThemeContext.d.ts +9 -0
  75. package/lib/ui/SessionUserInterfaceThemeContext.js +7 -0
  76. package/lib/ui/SessionUserInterfaceThemePreferenceContext.d.ts +3 -0
  77. package/lib/ui/SessionUserInterfaceThemePreferenceContext.js +7 -0
  78. package/lib/ui/SessionUserInterfaceWalkthroughLinkContext.d.ts +42 -0
  79. package/lib/ui/SessionUserInterfaceWalkthroughLinkContext.js +26 -0
  80. package/lib/ui/SessionUserInterfaceWrapper.d.ts +8 -0
  81. package/lib/ui/SessionUserInterfaceWrapper.js +321 -0
  82. package/lib/ui/SettingsEditorCallbacksContext.d.ts +3 -0
  83. package/lib/ui/SettingsEditorCallbacksContext.js +7 -0
  84. package/lib/ui/SettingsEditorContext.d.ts +33 -0
  85. package/lib/ui/SettingsEditorContext.js +16 -0
  86. package/lib/ui/SettingsEditorLocaleContext.d.ts +13 -0
  87. package/lib/ui/SettingsEditorLocaleContext.js +15 -0
  88. package/lib/ui/SettingsEditorThemeContext.d.ts +9 -0
  89. package/lib/ui/SettingsEditorThemeContext.js +7 -0
  90. package/lib/ui/SettingsEditorThemePreferenceContext.d.ts +3 -0
  91. package/lib/ui/SettingsEditorThemePreferenceContext.js +7 -0
  92. package/lib/ui/SettingsEditorWrapper.d.ts +13 -0
  93. package/lib/ui/SettingsEditorWrapper.js +74 -0
  94. package/lib/ui/UserInterfaceCallbacksContext.d.ts +3 -0
  95. package/lib/ui/UserInterfaceCallbacksContext.js +7 -0
  96. package/lib/ui/UserInterfaceDataContext.d.ts +78 -0
  97. package/lib/ui/UserInterfaceDataContext.js +11 -0
  98. package/lib/ui/UserInterfaceLocaleContext.d.ts +13 -0
  99. package/lib/ui/UserInterfaceLocaleContext.js +15 -0
  100. package/lib/ui/UserInterfaceOwnTeamContext.d.ts +25 -0
  101. package/lib/ui/UserInterfaceOwnTeamContext.js +26 -0
  102. package/lib/ui/UserInterfaceOwnTeamJoinLinkContext.d.ts +52 -0
  103. package/lib/ui/UserInterfaceOwnTeamJoinLinkContext.js +13 -0
  104. package/lib/ui/UserInterfaceParticipantContext.d.ts +16 -0
  105. package/lib/ui/UserInterfaceParticipantContext.js +24 -0
  106. package/lib/ui/UserInterfaceParticipantsContext.d.ts +12 -0
  107. package/lib/ui/UserInterfaceParticipantsContext.js +13 -0
  108. package/lib/ui/UserInterfaceRunKindContext.d.ts +31 -0
  109. package/lib/ui/UserInterfaceRunKindContext.js +31 -0
  110. package/lib/ui/UserInterfaceTeamsContext.d.ts +12 -0
  111. package/lib/ui/UserInterfaceTeamsContext.js +13 -0
  112. package/lib/ui/UserInterfaceThemeContext.d.ts +9 -0
  113. package/lib/ui/UserInterfaceThemeContext.js +7 -0
  114. package/lib/ui/UserInterfaceThemePreferenceContext.d.ts +3 -0
  115. package/lib/ui/UserInterfaceThemePreferenceContext.js +7 -0
  116. package/lib/ui/UserInterfaceTimerContext.d.ts +10 -0
  117. package/lib/ui/UserInterfaceTimerContext.js +12 -0
  118. package/lib/ui/UserInterfaceValidationContext.d.ts +25 -0
  119. package/lib/ui/UserInterfaceValidationContext.js +14 -0
  120. package/lib/ui/UserInterfaceViewerRoleContext.d.ts +14 -0
  121. package/lib/ui/UserInterfaceViewerRoleContext.js +17 -0
  122. package/lib/ui/UserInterfaceWrapper.d.ts +8 -0
  123. package/lib/ui/UserInterfaceWrapper.js +219 -0
  124. package/lib/ui/expectedParentOrigin.d.ts +10 -0
  125. package/lib/ui/expectedParentOrigin.js +32 -0
  126. package/lib/ui/index.d.ts +50 -0
  127. package/lib/ui/index.js +52 -0
  128. package/lib/ui/internal/SurfaceGate.d.ts +19 -0
  129. package/lib/ui/internal/SurfaceGate.js +17 -0
  130. package/lib/ui/internal/SurfaceScope.d.ts +17 -0
  131. package/lib/ui/internal/SurfaceScope.js +16 -0
  132. package/lib/ui/internal/createContexts.d.ts +50 -0
  133. package/lib/ui/internal/createContexts.js +32 -0
  134. package/lib/ui/internal/document-attributes.d.ts +9 -0
  135. package/lib/ui/internal/document-attributes.js +87 -0
  136. package/lib/ui/internal/surface-timer.d.ts +3 -0
  137. package/lib/ui/internal/surface-timer.js +11 -0
  138. package/lib/ui/internal/useLatestRef.d.ts +7 -0
  139. package/lib/ui/internal/useLatestRef.js +11 -0
  140. package/lib/ui/internal/useOptimisticMerge.d.ts +19 -0
  141. package/lib/ui/internal/useOptimisticMerge.js +28 -0
  142. package/lib/ui/internal/useSurfaceControls.d.ts +52 -0
  143. package/lib/ui/internal/useSurfaceControls.js +19 -0
  144. package/lib/ui/internal/useTypedPastState.d.ts +17 -0
  145. package/lib/ui/internal/useTypedPastState.js +17 -0
  146. package/lib/ui/mutation-commit.d.ts +32 -0
  147. package/lib/ui/mutation-commit.js +1 -0
  148. package/lib/ui/mutation-outcome.d.ts +66 -0
  149. package/lib/ui/mutation-outcome.js +49 -0
  150. package/lib/ui/request-id.d.ts +18 -0
  151. package/lib/ui/request-id.js +44 -0
  152. package/lib/ui/testing.d.ts +365 -0
  153. package/lib/ui/testing.js +572 -0
  154. package/lib/ui/tick-snapshot.d.ts +28 -0
  155. package/lib/ui/tick-snapshot.js +34 -0
  156. package/lib/ui/ui-protocol.d.ts +799 -0
  157. package/lib/ui/ui-protocol.js +50 -0
  158. package/lib/ui/useAsset.d.ts +80 -0
  159. package/lib/ui/useAsset.js +139 -0
  160. package/lib/ui/useEndpointCallbacks.d.ts +31 -0
  161. package/lib/ui/useEndpointCallbacks.js +68 -0
  162. package/lib/ui/useHostChannel.d.ts +90 -0
  163. package/lib/ui/useHostChannel.js +338 -0
  164. package/lib/ui/useMutationReporter.d.ts +100 -0
  165. package/lib/ui/useMutationReporter.js +97 -0
  166. package/lib/ui/usePastState.d.ts +39 -0
  167. package/lib/ui/usePastState.js +63 -0
  168. package/lib/ui/useViewerStorage.d.ts +54 -0
  169. package/lib/ui/useViewerStorage.js +141 -0
  170. package/package.json +89 -3
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Where to open the walkthrough run (§5.7).
3
+ *
4
+ * Facilitator-only, like {@link ./SessionUserInterfaceTeamLinksContext.TeamLinks},
5
+ * and unlike them it names no team: a walkthrough holds exactly one, so there
6
+ * is nothing to choose. The host sends it to the session surface and to nothing
7
+ * else.
8
+ */
9
+ export type WalkthroughLink = {
10
+ /**
11
+ * The URL to open, which mounts the walkthrough's player surface.
12
+ *
13
+ * Open it in a new tab or window rather than in the session surface's own
14
+ * canvas: a facilitator demonstrating puts this on a projector while
15
+ * keeping the session in front of them.
16
+ */
17
+ url: string;
18
+ };
19
+ export declare const SessionUserInterfaceWalkthroughLinkContext: import("react").Context<WalkthroughLink | null>;
20
+ /**
21
+ * Hook returning where to open the walkthrough, or `null` until the host has
22
+ * said.
23
+ *
24
+ * Every host offers a walkthrough (§5.7), so the link always arrives; `null` is
25
+ * only the moment before it does. Gate the control on the link, so a surface
26
+ * never shows a button with nowhere to go.
27
+ *
28
+ * ```tsx
29
+ * const walkthrough = useSessionUserInterfaceWalkthroughLink();
30
+ * const { resetWalkthrough } = useSessionUserInterfaceCallbacks();
31
+ *
32
+ * {walkthrough && (
33
+ * <>
34
+ * <Button href={walkthrough.url} target="_blank" rel="noreferrer">Walkthrough</Button>
35
+ * <Button onClick={() => void resetWalkthrough()}>Reset it</Button>
36
+ * </>
37
+ * )}
38
+ * ```
39
+ *
40
+ * @returns Where to open the walkthrough, or `null` before the host has said
41
+ */
42
+ export declare const useSessionUserInterfaceWalkthroughLink: () => WalkthroughLink | null;
@@ -0,0 +1,26 @@
1
+ import { createDefaultedContext } from './internal/createContexts.js';
2
+ const { Context, useValue } = createDefaultedContext(null);
3
+ export const SessionUserInterfaceWalkthroughLinkContext = Context;
4
+ /**
5
+ * Hook returning where to open the walkthrough, or `null` until the host has
6
+ * said.
7
+ *
8
+ * Every host offers a walkthrough (§5.7), so the link always arrives; `null` is
9
+ * only the moment before it does. Gate the control on the link, so a surface
10
+ * never shows a button with nowhere to go.
11
+ *
12
+ * ```tsx
13
+ * const walkthrough = useSessionUserInterfaceWalkthroughLink();
14
+ * const { resetWalkthrough } = useSessionUserInterfaceCallbacks();
15
+ *
16
+ * {walkthrough && (
17
+ * <>
18
+ * <Button href={walkthrough.url} target="_blank" rel="noreferrer">Walkthrough</Button>
19
+ * <Button onClick={() => void resetWalkthrough()}>Reset it</Button>
20
+ * </>
21
+ * )}
22
+ * ```
23
+ *
24
+ * @returns Where to open the walkthrough, or `null` before the host has said
25
+ */
26
+ export const useSessionUserInterfaceWalkthroughLink = useValue;
@@ -0,0 +1,8 @@
1
+ import { ReactNode } from 'react';
2
+ interface SessionUserInterfaceWrapperProps {
3
+ children?: ReactNode;
4
+ fallback?: ReactNode;
5
+ targetWindow?: Window;
6
+ }
7
+ declare function SessionUserInterfaceWrapper({ children, fallback, targetWindow, }: SessionUserInterfaceWrapperProps): import("react").JSX.Element;
8
+ export default SessionUserInterfaceWrapper;
@@ -0,0 +1,321 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useRef, useState } from 'react';
3
+ import { applyContextUpdate } from '../build/context-merge.js';
4
+ import { useDocumentAttributes } from './internal/document-attributes.js';
5
+ import { DEFAULT_SURFACE_TIMER } from './internal/surface-timer.js';
6
+ import { SurfaceGate } from './internal/SurfaceGate.js';
7
+ import { SurfaceScope } from './internal/SurfaceScope.js';
8
+ import { useLatestRef } from './internal/useLatestRef.js';
9
+ import { useSurfaceControls } from './internal/useSurfaceControls.js';
10
+ import { useTypedPastState } from './internal/useTypedPastState.js';
11
+ import { SessionUserInterfaceCallbacksContext } from './SessionUserInterfaceCallbacksContext.js';
12
+ import { SessionUserInterfaceDataContext, } from './SessionUserInterfaceDataContext.js';
13
+ import { SessionUserInterfaceHookFailuresContext, } from './SessionUserInterfaceHookFailuresContext.js';
14
+ import { SessionUserInterfaceHostOverlaysContext, } from './SessionUserInterfaceHostOverlaysContext.js';
15
+ import { SessionUserInterfaceLocaleContext, } from './SessionUserInterfaceLocaleContext.js';
16
+ import { SessionUserInterfaceMetaContext, } from './SessionUserInterfaceMetaContext.js';
17
+ import { SessionUserInterfaceParticipantsContext } from './SessionUserInterfaceParticipantsContext.js';
18
+ import { DEFAULT_TEAM_CONFIGURATION, SessionUserInterfaceTeamConfigurationContext, } from './SessionUserInterfaceTeamConfigurationContext.js';
19
+ import { SessionUserInterfaceTeamLinksContext, } from './SessionUserInterfaceTeamLinksContext.js';
20
+ import { SessionUserInterfaceTeamsContext, } from './SessionUserInterfaceTeamsContext.js';
21
+ import { SessionUserInterfaceThemeContext, } from './SessionUserInterfaceThemeContext.js';
22
+ import { SessionUserInterfaceThemePreferenceContext } from './SessionUserInterfaceThemePreferenceContext.js';
23
+ import { SessionUserInterfaceWalkthroughLinkContext, } from './SessionUserInterfaceWalkthroughLinkContext.js';
24
+ import { useHostChannel } from './useHostChannel.js';
25
+ import { UserInterfaceRunKindContext } from './UserInterfaceRunKindContext.js';
26
+ /**
27
+ * Teams, participants and join links are pushed but do not gate first paint: a
28
+ * surface can render its frame around an empty roster, and holding the whole
29
+ * canvas back for a join link nobody has scanned yet is worse than a
30
+ * placeholder.
31
+ *
32
+ * `setTeamConfiguration` is the exception among the pushed values, because its
33
+ * default cannot be rendered honestly. A surface that paints before bounds
34
+ * arrive holds `max: 0`, and the obvious `teams.length < max` check then reads
35
+ * as 'this session is full' and disables the control it guards (#365). There is
36
+ * no placeholder for a bound, so the surface waits for the real one.
37
+ */
38
+ const REQUIRED_MESSAGES = [
39
+ 'setSessionMeta',
40
+ 'setSessionData',
41
+ 'setSettings',
42
+ 'setTheme',
43
+ 'setLocale',
44
+ 'setAssetUrls',
45
+ 'setTeamConfiguration',
46
+ ];
47
+ /**
48
+ * Merge one team's delivered state into the session view.
49
+ *
50
+ * The revision decides what happens, and the three cases are all reachable in
51
+ * practice:
52
+ *
53
+ * - **In sequence** (exactly one ahead of what is held, and a first delivery
54
+ * for a team added mid-session counts): apply it.
55
+ * - **Behind or equal**: a redelivery, or a delta that raced the snapshot that
56
+ * already superseded it. Ignore it. Applying it would move the surface
57
+ * backwards, which is worse than doing nothing.
58
+ * - **Ahead by more than one**: an update was lost. The held state for that
59
+ * team is now unreliable and cannot be repaired from a delta, so the caller
60
+ * asks the host for a fresh snapshot.
61
+ *
62
+ * Returning `null` for the gap case keeps the decision here, where the rule is
63
+ * written down, rather than splitting it between this function and its caller.
64
+ */
65
+ function mergeTeamState(previous, teamId, state) {
66
+ const held = previous.teams[teamId]?.revision ?? 0;
67
+ if (state.revision <= held)
68
+ return previous;
69
+ if (state.revision > held + 1)
70
+ return null;
71
+ return { ...previous, teams: { ...previous.teams, [teamId]: state } };
72
+ }
73
+ /** {@link mergeTeamState} for the shared store, by the same three cases. */
74
+ function mergeSharedState(previous, state) {
75
+ const held = previous.shared.revision;
76
+ if (state.revision <= held)
77
+ return previous;
78
+ if (state.revision > held + 1)
79
+ return null;
80
+ return { ...previous, shared: state };
81
+ }
82
+ /** An empty `deletes` is left off the wire, as a player surface leaves it off. */
83
+ function deletesField(deletes) {
84
+ return deletes?.length ? { deletes } : {};
85
+ }
86
+ const EMPTY_SESSION_DATA = { shared: { context: {}, revision: 0 }, teams: {} };
87
+ function SessionUserInterfaceWrapper({ children, fallback = _jsx("div", { children: "Loading..." }), targetWindow = window.parent, }) {
88
+ const [sessionData, setSessionDataState] = useState(EMPTY_SESSION_DATA);
89
+ const [settings, setSettingsState] = useState(undefined);
90
+ const [theme, setThemeState] = useState(undefined);
91
+ const [locale, setLocaleState] = useState(undefined);
92
+ const [participants, setParticipantsState] = useState([]);
93
+ const [teams, setTeamsState] = useState([]);
94
+ const [sessionMeta, setSessionMetaState] = useState(undefined);
95
+ const [teamLinks, setTeamLinksState] = useState([]);
96
+ // Fixed for the life of the run; 'session' is by far the common answer,
97
+ // so it never gates first paint.
98
+ const [runKind, setRunKindState] = useState('session');
99
+ // null until the host answers: a surface gates its control on this.
100
+ const [walkthroughLink, setWalkthroughLinkState] = useState(null);
101
+ const [hookFailures, setHookFailures] = useState([]);
102
+ const [teamConfiguration, setTeamConfigurationState] = useState(DEFAULT_TEAM_CONFIGURATION);
103
+ // Empty is the honest default and also the right answer for a host that
104
+ // delegates none, so this does not gate first paint.
105
+ const [hostOverlays, setHostOverlaysState] = useState([]);
106
+ /**
107
+ * Whether a `resyncSessionData` is outstanding. A gap usually means several
108
+ * deltas were lost, so without this one lost update would send a request
109
+ * per subsequent delta until the snapshot landed. Cleared by the snapshot
110
+ * arriving, which is the only thing that resolves the gap.
111
+ */
112
+ const resyncPendingRef = useRef(false);
113
+ const { isInitialized, expectedParentOrigin, postMessage, sendMutation, uploadBlob, readAsset, getPastState: getPastStateRaw, getAssetUrl, assetUrls, setThemePreference, endpoints, themePreference, sessionKey, } = useHostChannel({
114
+ targetWindow,
115
+ label: 'SessionUserInterfaceWrapper',
116
+ requiredMessages: REQUIRED_MESSAGES,
117
+ onMessage: (msg, { markReceived }) => {
118
+ if (msg.type === 'setSessionData') {
119
+ setSessionDataState({
120
+ shared: msg.payload?.shared ?? EMPTY_SESSION_DATA.shared,
121
+ teams: msg.payload?.teams ?? {},
122
+ });
123
+ // A snapshot is by definition current, so any resync it was
124
+ // sent in answer to is now settled.
125
+ resyncPendingRef.current = false;
126
+ markReceived('setSessionData');
127
+ }
128
+ else if (msg.type === 'updateTeamState') {
129
+ const merged = mergeTeamState(sessionDataRef.current, msg.teamId, msg.state);
130
+ if (merged === null) {
131
+ requestResync();
132
+ return;
133
+ }
134
+ sessionDataRef.current = merged;
135
+ setSessionDataState(merged);
136
+ }
137
+ else if (msg.type === 'updateSharedState') {
138
+ const merged = mergeSharedState(sessionDataRef.current, msg.state);
139
+ if (merged === null) {
140
+ requestResync();
141
+ return;
142
+ }
143
+ sessionDataRef.current = merged;
144
+ setSessionDataState(merged);
145
+ }
146
+ else if (msg.type === 'setSessionMeta') {
147
+ setSessionMetaState(msg.payload);
148
+ markReceived('setSessionMeta');
149
+ }
150
+ else if (msg.type === 'setSettings') {
151
+ setSettingsState(msg.payload);
152
+ markReceived('setSettings');
153
+ }
154
+ else if (msg.type === 'setTheme') {
155
+ setThemeState(msg.payload);
156
+ markReceived('setTheme');
157
+ }
158
+ else if (msg.type === 'setLocale') {
159
+ setLocaleState(msg.payload);
160
+ markReceived('setLocale');
161
+ }
162
+ else if (msg.type === 'setTeamList') {
163
+ setTeamsState(msg.payload);
164
+ }
165
+ else if (msg.type === 'setTeamConfiguration') {
166
+ setTeamConfigurationState(msg.payload);
167
+ markReceived('setTeamConfiguration');
168
+ }
169
+ else if (msg.type === 'setRunKind') {
170
+ setRunKindState(msg.runKind);
171
+ }
172
+ else if (msg.type === 'setWalkthroughLink') {
173
+ setWalkthroughLinkState(msg.payload);
174
+ }
175
+ else if (msg.type === 'setTeamLinks') {
176
+ setTeamLinksState(msg.payload);
177
+ }
178
+ else if (msg.type === 'setAvailableOverlays') {
179
+ setHostOverlaysState(msg.overlays);
180
+ }
181
+ else if (msg.type === 'hookFailed') {
182
+ // Accumulate: a facilitator who looked away should still find
183
+ // out that something went wrong.
184
+ setHookFailures((prev) => [...prev, msg.payload]);
185
+ }
186
+ else if (msg.type === 'setParticipantList') {
187
+ setParticipantsState(msg.participants);
188
+ }
189
+ },
190
+ });
191
+ // Mirror the latest optimistic session data so setData can merge
192
+ // synchronously and return a correctly-correlated promise (a state updater
193
+ // would run too late to capture the sendMutation promise). The delta
194
+ // handler reads it too, so two updates arriving before a re-render each
195
+ // merge onto the previous one rather than onto stale state.
196
+ const sessionDataRef = useLatestRef(sessionData);
197
+ /**
198
+ * Ask the host for a fresh full snapshot after a per-team revision arrived
199
+ * out of sequence. The surface keeps rendering what it holds meanwhile: one
200
+ * team's state being a beat stale is better than blanking a facilitator's
201
+ * screen, and the snapshot repairs it either way.
202
+ */
203
+ const requestResync = useCallback(() => {
204
+ if (resyncPendingRef.current)
205
+ return;
206
+ resyncPendingRef.current = true;
207
+ postMessage({ type: 'resyncSessionData' });
208
+ }, [postMessage]);
209
+ const setData = useCallback((teamIdOrUpdate, updateOrOptions, maybeOptions) => {
210
+ const prev = sessionDataRef.current;
211
+ if (typeof teamIdOrUpdate !== 'string') {
212
+ // Shared write: one value for the session, so it merges into
213
+ // the shared store the surface holds once.
214
+ const update = teamIdOrUpdate;
215
+ const deletes = updateOrOptions?.deletes;
216
+ const next = {
217
+ ...prev,
218
+ shared: {
219
+ ...prev.shared,
220
+ context: applyContextUpdate(prev.shared.context, update, deletes),
221
+ },
222
+ };
223
+ sessionDataRef.current = next;
224
+ setSessionDataState(next);
225
+ return sendMutation({
226
+ type: 'updateSharedData',
227
+ payload: update,
228
+ ...deletesField(deletes),
229
+ });
230
+ }
231
+ const teamId = teamIdOrUpdate;
232
+ const update = updateOrOptions ?? {};
233
+ const deletes = maybeOptions?.deletes;
234
+ const teamState = prev.teams[teamId] ?? {
235
+ context: {},
236
+ completedTicks: 0,
237
+ timer: DEFAULT_SURFACE_TIMER,
238
+ revision: 0,
239
+ };
240
+ const mergedContext = applyContextUpdate(teamState.context, update, deletes);
241
+ const next = {
242
+ ...prev,
243
+ teams: {
244
+ ...prev.teams,
245
+ [teamId]: { ...teamState, context: mergedContext },
246
+ },
247
+ };
248
+ sessionDataRef.current = next;
249
+ setSessionDataState(next);
250
+ return sendMutation({
251
+ type: 'updateSessionData',
252
+ teamId,
253
+ payload: update,
254
+ ...deletesField(deletes),
255
+ });
256
+ }, [sendMutation]);
257
+ const requestTick = useCallback((teamId) => sendMutation({ type: 'requestTick', teamId }), [sendMutation]);
258
+ const getPastState = useTypedPastState(getPastStateRaw);
259
+ useDocumentAttributes(locale?.locale, theme?.mode);
260
+ const { pauseTimer, resumeTimer, resetGame, undoTick, extendTick, openHostOverlay } = useSurfaceControls({ sendMutation });
261
+ // Session-wide, so it is not part of useSurfaceControls, whose messages all
262
+ // carry an optional teamId.
263
+ const setWaitingRoom = useCallback((open) => sendMutation({ type: 'setWaitingRoom', open }), [sendMutation]);
264
+ const resetWalkthrough = useCallback(() => sendMutation({ type: 'resetWalkthrough' }), [sendMutation]);
265
+ const createTeam = useCallback((name) => sendMutation({ type: 'createTeam', name }), [sendMutation]);
266
+ const renameTeam = useCallback((teamId, name) => sendMutation({ type: 'renameTeam', teamId, name }), [sendMutation]);
267
+ const renameParticipant = useCallback((participantId, name) => sendMutation({ type: 'renameParticipant', participantId, name }), [sendMutation]);
268
+ const moveParticipant = useCallback((participantId, teamId) => sendMutation({ type: 'moveParticipant', participantId, teamId }), [sendMutation]);
269
+ const deleteTeam = useCallback((teamId) => sendMutation({ type: 'deleteTeam', teamId }), [sendMutation]);
270
+ const hookFailuresValue = useMemo(() => ({ failures: hookFailures, clearFailures: () => setHookFailures([]) }), [hookFailures]);
271
+ const dataContextValue = useMemo(() => ({ sessionData, settings, setData }), [sessionData, settings, setData]);
272
+ const callbacksContextValue = useMemo(() => ({
273
+ requestTick,
274
+ uploadBlob,
275
+ getAssetUrl,
276
+ readAsset,
277
+ getPastState,
278
+ setThemePreference,
279
+ pauseTimer,
280
+ resumeTimer,
281
+ resetGame,
282
+ undoTick,
283
+ extendTick,
284
+ openHostOverlay,
285
+ setWaitingRoom,
286
+ resetWalkthrough,
287
+ createTeam,
288
+ renameTeam,
289
+ renameParticipant,
290
+ moveParticipant,
291
+ deleteTeam,
292
+ endpoints,
293
+ }), [
294
+ requestTick,
295
+ uploadBlob,
296
+ getAssetUrl,
297
+ readAsset,
298
+ getPastState,
299
+ setThemePreference,
300
+ pauseTimer,
301
+ resumeTimer,
302
+ resetGame,
303
+ undoTick,
304
+ extendTick,
305
+ openHostOverlay,
306
+ setWaitingRoom,
307
+ resetWalkthrough,
308
+ createTeam,
309
+ renameTeam,
310
+ renameParticipant,
311
+ moveParticipant,
312
+ deleteTeam,
313
+ endpoints,
314
+ ]);
315
+ const storageScope = useMemo(() => ({ sessionKey, surface: 'session', viewer: 'facilitator' }), [sessionKey]);
316
+ return (_jsx(SurfaceGate, { expectedParentOrigin: expectedParentOrigin, ready: isInitialized &&
317
+ theme !== undefined &&
318
+ locale !== undefined &&
319
+ sessionMeta !== undefined, fallback: fallback, children: _jsx(SurfaceScope, { storage: storageScope, assetUrls: assetUrls, getAssetUrl: getAssetUrl, locale: locale?.locale, getPastState: getPastState, children: _jsx(SessionUserInterfaceDataContext.Provider, { value: dataContextValue, children: _jsx(SessionUserInterfaceThemeContext.Provider, { value: theme, children: _jsx(SessionUserInterfaceThemePreferenceContext.Provider, { value: themePreference, children: _jsx(SessionUserInterfaceLocaleContext.Provider, { value: locale, children: _jsx(SessionUserInterfaceMetaContext.Provider, { value: sessionMeta, children: _jsx(SessionUserInterfaceTeamsContext.Provider, { value: teams, children: _jsx(SessionUserInterfaceTeamConfigurationContext.Provider, { value: teamConfiguration, children: _jsx(UserInterfaceRunKindContext.Provider, { value: runKind, children: _jsx(SessionUserInterfaceWalkthroughLinkContext.Provider, { value: walkthroughLink, children: _jsx(SessionUserInterfaceTeamLinksContext.Provider, { value: teamLinks, children: _jsx(SessionUserInterfaceHostOverlaysContext.Provider, { value: hostOverlays, children: _jsx(SessionUserInterfaceParticipantsContext.Provider, { value: participants, children: _jsx(SessionUserInterfaceCallbacksContext.Provider, { value: callbacksContextValue, children: _jsx(SessionUserInterfaceHookFailuresContext.Provider, { value: hookFailuresValue, children: children }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) }));
320
+ }
321
+ export default SessionUserInterfaceWrapper;
@@ -0,0 +1,3 @@
1
+ import type { SettingsEditorCallbacks } from '../build/callbacks.js';
2
+ export declare const SettingsEditorCallbacksContext: import("react").Context<SettingsEditorCallbacks | undefined>;
3
+ export declare const useSettingsEditorCallbacks: () => SettingsEditorCallbacks;
@@ -0,0 +1,7 @@
1
+ import { createRequiredContext } from './internal/createContexts.js';
2
+ const { Context, useValue } = createRequiredContext({
3
+ hookName: 'useSettingsEditorCallbacks',
4
+ wrapperName: 'SettingsEditorWrapper',
5
+ });
6
+ export const SettingsEditorCallbacksContext = Context;
7
+ export const useSettingsEditorCallbacks = useValue;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What a settings editor edits: the `setup` slot and the `scenario_editor`
3
+ * slot share it (§6.5).
4
+ */
5
+ export type SettingsEditorValue<T> = {
6
+ /**
7
+ * The resolved values of the settings this editor `configures`, keyed by
8
+ * settings identifier, with any edits made so far merged in.
9
+ */
10
+ settings: T;
11
+ /**
12
+ * Merge an update into the settings. Only what changed reaches the host
13
+ * (§8.2). Resolves once the host confirms the write; rejects with a
14
+ * `MutationError` when it is refused (settings validation, say) or fails.
15
+ *
16
+ * Await it: a screen that drops a refusal leaves the facilitator believing
17
+ * a setting took when it did not, and they will not find out until the
18
+ * session runs. `useMutationReporter` does the awaiting and hands you the
19
+ * failure to render.
20
+ */
21
+ updateSettings: (update: Partial<T>) => Promise<void>;
22
+ /** Identifiers of the settings resources this editor edits. */
23
+ configures: string[];
24
+ };
25
+ declare const SettingsEditorContext: import("react").Context<SettingsEditorValue<unknown> | undefined>;
26
+ /**
27
+ * The settings a `setup` or `scenario_editor` slot edits, and the way to
28
+ * change them. Pass the generated `Settings` type from the slot's `types.ts`.
29
+ *
30
+ * @throws Error if used outside SettingsEditorWrapper
31
+ */
32
+ declare function useSettingsEditor<T>(): SettingsEditorValue<T>;
33
+ export { useSettingsEditor, SettingsEditorContext };
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext } from 'react';
2
+ const SettingsEditorContext = createContext(undefined);
3
+ /**
4
+ * The settings a `setup` or `scenario_editor` slot edits, and the way to
5
+ * change them. Pass the generated `Settings` type from the slot's `types.ts`.
6
+ *
7
+ * @throws Error if used outside SettingsEditorWrapper
8
+ */
9
+ function useSettingsEditor() {
10
+ const ctx = useContext(SettingsEditorContext);
11
+ if (!ctx) {
12
+ throw new Error('useSettingsEditor must be used inside a SettingsEditorWrapper');
13
+ }
14
+ return ctx;
15
+ }
16
+ export { useSettingsEditor, SettingsEditorContext };
@@ -0,0 +1,13 @@
1
+ export type SettingsEditorLocale = {
2
+ locale: string;
3
+ };
4
+ export declare const SettingsEditorLocaleContext: import("react").Context<SettingsEditorLocale | undefined>;
5
+ /**
6
+ * Hook to access the current locale in a settings editor (the `setup` or
7
+ * `scenario_editor` slot). The host picks it from the game's supported
8
+ * locales.
9
+ *
10
+ * @returns Current locale object with locale string
11
+ * @throws Error if used outside SettingsEditorWrapper
12
+ */
13
+ export declare const useSettingsEditorLocale: () => SettingsEditorLocale;
@@ -0,0 +1,15 @@
1
+ import { createRequiredContext } from './internal/createContexts.js';
2
+ const { Context, useValue } = createRequiredContext({
3
+ hookName: 'useSettingsEditorLocale',
4
+ wrapperName: 'SettingsEditorWrapper',
5
+ });
6
+ export const SettingsEditorLocaleContext = Context;
7
+ /**
8
+ * Hook to access the current locale in a settings editor (the `setup` or
9
+ * `scenario_editor` slot). The host picks it from the game's supported
10
+ * locales.
11
+ *
12
+ * @returns Current locale object with locale string
13
+ * @throws Error if used outside SettingsEditorWrapper
14
+ */
15
+ export const useSettingsEditorLocale = useValue;
@@ -0,0 +1,9 @@
1
+ import { type SurfaceModeColors } from './internal/createContexts.js';
2
+ export type SettingsEditorModeColors = SurfaceModeColors;
3
+ export type SettingsEditorTheme = {
4
+ mode: 'dark' | 'light';
5
+ dark: SettingsEditorModeColors;
6
+ light: SettingsEditorModeColors;
7
+ };
8
+ export declare const SettingsEditorThemeContext: import("react").Context<SettingsEditorTheme | undefined>;
9
+ export declare const useSettingsEditorTheme: () => SettingsEditorTheme;
@@ -0,0 +1,7 @@
1
+ import { createRequiredContext } from './internal/createContexts.js';
2
+ const { Context, useValue } = createRequiredContext({
3
+ hookName: 'useSettingsEditorTheme',
4
+ wrapperName: 'SettingsEditorWrapper',
5
+ });
6
+ export const SettingsEditorThemeContext = Context;
7
+ export const useSettingsEditorTheme = useValue;
@@ -0,0 +1,3 @@
1
+ import type { ThemePreference } from '../build/callbacks.js';
2
+ export declare const SettingsEditorThemePreferenceContext: import("react").Context<ThemePreference | undefined>;
3
+ export declare const useSettingsEditorThemePreference: () => ThemePreference;
@@ -0,0 +1,7 @@
1
+ import { createRequiredContext } from './internal/createContexts.js';
2
+ const { Context, useValue } = createRequiredContext({
3
+ hookName: 'useSettingsEditorThemePreference',
4
+ wrapperName: 'SettingsEditorWrapper',
5
+ });
6
+ export const SettingsEditorThemePreferenceContext = Context;
7
+ export const useSettingsEditorThemePreference = useValue;
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ interface SettingsEditorWrapperProps {
3
+ children?: ReactNode;
4
+ fallback?: ReactNode;
5
+ targetWindow?: Window;
6
+ }
7
+ /**
8
+ * The wrapper for both settings editors, the `setup` slot and the
9
+ * `scenario_editor` slot (§6.5). Each slot is its own bundle, so a screen
10
+ * knows which it is without being told: the host sends the same batch to both.
11
+ */
12
+ declare function SettingsEditorWrapper({ children, fallback, targetWindow, }: SettingsEditorWrapperProps): import("react").JSX.Element;
13
+ export default SettingsEditorWrapper;
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useRef, useState } from 'react';
3
+ import { changedFrom, deepMergeContext } from '../build/context-merge.js';
4
+ import { useDocumentAttributes } from './internal/document-attributes.js';
5
+ import { SurfaceGate } from './internal/SurfaceGate.js';
6
+ import { SurfaceScope } from './internal/SurfaceScope.js';
7
+ import { SettingsEditorCallbacksContext } from './SettingsEditorCallbacksContext.js';
8
+ import { SettingsEditorContext } from './SettingsEditorContext.js';
9
+ import { SettingsEditorLocaleContext, } from './SettingsEditorLocaleContext.js';
10
+ import { SettingsEditorThemeContext } from './SettingsEditorThemeContext.js';
11
+ import { SettingsEditorThemePreferenceContext } from './SettingsEditorThemePreferenceContext.js';
12
+ import { useHostChannel } from './useHostChannel.js';
13
+ const REQUIRED_MESSAGES = ['setSettings', 'setTheme', 'setLocale', 'setAssetUrls'];
14
+ /**
15
+ * The wrapper for both settings editors, the `setup` slot and the
16
+ * `scenario_editor` slot (§6.5). Each slot is its own bundle, so a screen
17
+ * knows which it is without being told: the host sends the same batch to both.
18
+ */
19
+ function SettingsEditorWrapper({ children, fallback = _jsx("div", { children: "Loading..." }), targetWindow = window.parent, }) {
20
+ const [settings, setSettingsState] = useState(undefined);
21
+ const [configures, setConfigures] = useState([]);
22
+ const [theme, setThemeState] = useState(undefined);
23
+ const [locale, setLocaleState] = useState(undefined);
24
+ const { isInitialized, expectedParentOrigin, postMessage, sendMutation, uploadBlob, readAsset, getAssetUrl, assetUrls, setThemePreference, endpoints, themePreference, sessionKey, } = useHostChannel({
25
+ targetWindow,
26
+ label: 'SettingsEditorWrapper',
27
+ requiredMessages: REQUIRED_MESSAGES,
28
+ onMessage: (msg, { markReceived }) => {
29
+ if (msg.type === 'setSettings') {
30
+ setSettingsState(msg.payload);
31
+ // `setSettings` carries exactly what this editor configures
32
+ // (§8.3), so its keys are the list: there is no second message
33
+ // to drift from it.
34
+ setConfigures(Object.keys(msg.payload));
35
+ markReceived('setSettings');
36
+ }
37
+ else if (msg.type === 'setTheme') {
38
+ setThemeState(msg.payload);
39
+ markReceived('setTheme');
40
+ }
41
+ else if (msg.type === 'setLocale') {
42
+ setLocaleState(msg.payload);
43
+ markReceived('setLocale');
44
+ }
45
+ },
46
+ });
47
+ // Mirror the latest optimistic settings so updateSettings can merge
48
+ // synchronously and return a correctly-correlated promise (a state updater
49
+ // would run too late to capture the sendMutation promise).
50
+ const settingsRef = useRef(undefined);
51
+ settingsRef.current = settings;
52
+ const updateSettings = useCallback((update) => {
53
+ const current = settingsRef.current ?? {};
54
+ const merged = deepMergeContext(current, update);
55
+ // Only what changed goes to the host (§8.2): a UI that spreads the
56
+ // settings it was given would otherwise send every value back as
57
+ // a facilitator override.
58
+ const changed = changedFrom(update, current) ?? {};
59
+ settingsRef.current = merged;
60
+ setSettingsState(merged);
61
+ return sendMutation({ type: 'updateSettings', payload: changed });
62
+ }, [sendMutation]);
63
+ useDocumentAttributes(locale?.locale, theme?.mode);
64
+ const requestPlay = useCallback(() => postMessage({ type: 'requestPlay' }), [postMessage]);
65
+ const editorContextValue = useMemo(() => ({ settings, updateSettings, configures }), [settings, updateSettings, configures]);
66
+ const callbacksContextValue = useMemo(() => ({ uploadBlob, getAssetUrl, readAsset, setThemePreference, endpoints, requestPlay }), [uploadBlob, getAssetUrl, readAsset, setThemePreference, endpoints, requestPlay]);
67
+ const storageScope = useMemo(() => ({
68
+ sessionKey,
69
+ surface: 'settings-editor',
70
+ viewer: 'facilitator',
71
+ }), [sessionKey]);
72
+ return (_jsx(SurfaceGate, { expectedParentOrigin: expectedParentOrigin, ready: isInitialized && theme !== undefined && locale !== undefined, fallback: fallback, children: _jsx(SurfaceScope, { storage: storageScope, assetUrls: assetUrls, getAssetUrl: getAssetUrl, locale: locale?.locale, children: _jsx(SettingsEditorContext.Provider, { value: editorContextValue, children: _jsx(SettingsEditorThemeContext.Provider, { value: theme, children: _jsx(SettingsEditorThemePreferenceContext.Provider, { value: themePreference, children: _jsx(SettingsEditorLocaleContext.Provider, { value: locale, children: _jsx(SettingsEditorCallbacksContext.Provider, { value: callbacksContextValue, children: children }) }) }) }) }) }) }));
73
+ }
74
+ export default SettingsEditorWrapper;
@@ -0,0 +1,3 @@
1
+ import type { UserInterfaceCallbacks } from '../build/callbacks.js';
2
+ export declare const UserInterfaceCallbacksContext: import("react").Context<UserInterfaceCallbacks | undefined>;
3
+ export declare const useUserInterfaceCallbacks: () => UserInterfaceCallbacks;
@@ -0,0 +1,7 @@
1
+ import { createRequiredContext } from './internal/createContexts.js';
2
+ const { Context, useValue } = createRequiredContext({
3
+ hookName: 'useUserInterfaceCallbacks',
4
+ wrapperName: 'UserInterfaceWrapper',
5
+ });
6
+ export const UserInterfaceCallbacksContext = Context;
7
+ export const useUserInterfaceCallbacks = useValue;