@seriousgaming/sdk-tools 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 (391) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +38 -2
  3. package/dist/bundling/logic-exports.d.ts +34 -0
  4. package/dist/bundling/node-builtins.d.ts +21 -0
  5. package/dist/bundling/transpiler.d.ts +7 -0
  6. package/dist/cli.js +27404 -0
  7. package/dist/client/dev-server.d.ts +59 -0
  8. package/dist/client/index.d.ts +28 -0
  9. package/dist/client/stub-services.d.ts +32 -0
  10. package/dist/client/test-session.d.ts +195 -0
  11. package/dist/client.js +16920 -0
  12. package/dist/core/asset-reference-translator.d.ts +46 -0
  13. package/dist/core/clock.d.ts +57 -0
  14. package/dist/core/faults.d.ts +65 -0
  15. package/dist/core/game-settings.d.ts +65 -0
  16. package/dist/core/history.d.ts +89 -0
  17. package/dist/core/listen-with-fallback.d.ts +30 -0
  18. package/dist/core/mutation-queue.d.ts +61 -0
  19. package/dist/core/package-scenario.d.ts +89 -0
  20. package/dist/core/participant-registry.d.ts +109 -0
  21. package/dist/core/pending-changes.d.ts +14 -0
  22. package/dist/core/protocol.d.ts +83 -0
  23. package/dist/core/scenario-drafts.d.ts +32 -0
  24. package/dist/core/session-endpoints.d.ts +108 -0
  25. package/dist/core/session-gates.d.ts +75 -0
  26. package/dist/core/session-host.d.ts +94 -0
  27. package/dist/core/session-refusals.d.ts +56 -0
  28. package/dist/core/session-rewind.d.ts +32 -0
  29. package/dist/core/session-roster.d.ts +128 -0
  30. package/dist/core/session-surfaces.d.ts +32 -0
  31. package/dist/core/session-ticks.d.ts +18 -0
  32. package/dist/core/session-timers.d.ts +84 -0
  33. package/dist/core/session-writes.d.ts +40 -0
  34. package/dist/core/session.d.ts +385 -0
  35. package/dist/core/settings-locks.d.ts +22 -0
  36. package/dist/core/team-names.d.ts +52 -0
  37. package/dist/core/team-state-store.d.ts +218 -0
  38. package/dist/core/tick-timer.d.ts +75 -0
  39. package/dist/dev/blob-links.d.ts +10 -0
  40. package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
  41. package/dist/dev/console/index.html +12 -0
  42. package/dist/dev/console-path.d.ts +19 -0
  43. package/dist/dev/lockfile.d.ts +124 -0
  44. package/dist/dev/package-watcher.d.ts +21 -0
  45. package/dist/dev/routes.d.ts +67 -0
  46. package/dist/dev/shell.d.ts +260 -0
  47. package/dist/dev/surfaces.d.ts +41 -0
  48. package/dist/dev/tools-identity.d.ts +23 -0
  49. package/dist/dev/view/batch.d.ts +172 -0
  50. package/dist/dev/view/commands.d.ts +75 -0
  51. package/dist/dev/view/route.d.ts +58 -0
  52. package/dist/dev/view/view.d.ts +78 -0
  53. package/dist/dev/view/viewer.d.ts +32 -0
  54. package/dist/discovery/config.d.ts +9 -0
  55. package/dist/discovery/manifest-paths.d.ts +21 -0
  56. package/dist/discovery/removed-fields.d.ts +90 -0
  57. package/dist/docs/api/deck.md +386 -0
  58. package/dist/docs/api/index.md +67 -0
  59. package/dist/docs/api/sdk-commands.md +286 -0
  60. package/dist/docs/api/sdk-e2e.md +433 -0
  61. package/dist/docs/api/sdk-projection.md +207 -0
  62. package/dist/docs/api/sdk-testing.md +214 -0
  63. package/dist/docs/api/sdk-tools-client.md +560 -0
  64. package/dist/docs/api/sdk-ui-testing.md +231 -0
  65. package/dist/docs/api/sdk-ui.md +1269 -0
  66. package/dist/docs/api/sdk.md +2140 -0
  67. package/dist/docs/changelog-0.x.md +1156 -0
  68. package/dist/docs/changelog.md +241 -0
  69. package/dist/docs/cli-and-dev-server.md +217 -0
  70. package/dist/docs/cli-error-codes.md +156 -0
  71. package/dist/docs/cli-reference.md +2489 -0
  72. package/dist/docs/cli-simulation-testing.md +222 -0
  73. package/dist/docs/concepts/index.md +46 -0
  74. package/dist/docs/concepts/terminology.md +322 -0
  75. package/dist/docs/environment-variables.md +102 -0
  76. package/dist/docs/getting-started.md +145 -0
  77. package/dist/docs/guides/ai-facilitation.md +220 -0
  78. package/dist/docs/guides/capabilities.md +460 -0
  79. package/dist/docs/guides/endpoints.md +335 -0
  80. package/dist/docs/guides/engine-testing.md +477 -0
  81. package/dist/docs/guides/engine.md +527 -0
  82. package/dist/docs/guides/file-handling.md +405 -0
  83. package/dist/docs/guides/games.md +176 -0
  84. package/dist/docs/guides/global-settings.md +94 -0
  85. package/dist/docs/guides/guide.md +123 -0
  86. package/dist/docs/guides/hooks.md +183 -0
  87. package/dist/docs/guides/index.md +51 -0
  88. package/dist/docs/guides/internationalization.md +288 -0
  89. package/dist/docs/guides/llm-integration.md +635 -0
  90. package/dist/docs/guides/multi-team-games.md +586 -0
  91. package/dist/docs/guides/patterns.md +391 -0
  92. package/dist/docs/guides/presentation.md +196 -0
  93. package/dist/docs/guides/production-constraints.md +130 -0
  94. package/dist/docs/guides/scenario-editor.md +143 -0
  95. package/dist/docs/guides/scenarios.md +515 -0
  96. package/dist/docs/guides/session.md +972 -0
  97. package/dist/docs/guides/settings.md +458 -0
  98. package/dist/docs/guides/setup.md +193 -0
  99. package/dist/docs/guides/speech-generation.md +233 -0
  100. package/dist/docs/guides/starter-game.md +164 -0
  101. package/dist/docs/guides/testing.md +577 -0
  102. package/dist/docs/guides/tick-controller.md +569 -0
  103. package/dist/docs/guides/type-safety.md +516 -0
  104. package/dist/docs/guides/ui.md +1389 -0
  105. package/dist/docs/guides/upgrading.md +636 -0
  106. package/dist/docs/guides/validators.md +217 -0
  107. package/dist/docs/guides/waiting-room.md +244 -0
  108. package/dist/docs/index.md +41 -0
  109. package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
  110. package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
  111. package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
  112. package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
  113. package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
  114. package/dist/docs/recipes/create-capability.md +66 -0
  115. package/dist/docs/recipes/create-endpoint.md +127 -0
  116. package/dist/docs/recipes/create-engine.md +111 -0
  117. package/dist/docs/recipes/create-game.md +70 -0
  118. package/dist/docs/recipes/create-guide.md +75 -0
  119. package/dist/docs/recipes/create-hook.md +80 -0
  120. package/dist/docs/recipes/create-presentation.md +142 -0
  121. package/dist/docs/recipes/create-scenario-editor.md +102 -0
  122. package/dist/docs/recipes/create-scenario.md +98 -0
  123. package/dist/docs/recipes/create-session-ui.md +116 -0
  124. package/dist/docs/recipes/create-settings.md +88 -0
  125. package/dist/docs/recipes/create-setup.md +115 -0
  126. package/dist/docs/recipes/create-tick-controller.md +65 -0
  127. package/dist/docs/recipes/create-user-interface.md +96 -0
  128. package/dist/docs/recipes/create-validator.md +67 -0
  129. package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
  130. package/dist/docs/recipes/declare-api-uses.md +67 -0
  131. package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
  132. package/dist/docs/recipes/distribute-package.md +80 -0
  133. package/dist/docs/recipes/e2e-testing.md +116 -0
  134. package/dist/docs/recipes/end-game-on-condition.md +154 -0
  135. package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
  136. package/dist/docs/recipes/index.md +86 -0
  137. package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
  138. package/dist/docs/recipes/negotiate-across-teams.md +164 -0
  139. package/dist/docs/recipes/observe-other-teams-state.md +119 -0
  140. package/dist/docs/recipes/publish-a-package.md +139 -0
  141. package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
  142. package/dist/docs/recipes/review-a-past-day.md +73 -0
  143. package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
  144. package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
  145. package/dist/docs/recipes/show-all-teams-participants.md +109 -0
  146. package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
  147. package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
  148. package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
  149. package/dist/docs/recipes/test-user-interface.md +95 -0
  150. package/dist/docs/recipes/test-with-engine.md +115 -0
  151. package/dist/docs/recipes/upload-and-display-files.md +148 -0
  152. package/dist/docs/recipes/use-request-play.md +65 -0
  153. package/dist/docs/recipes/use-request-tick.md +75 -0
  154. package/dist/docs/troubleshooting.md +816 -0
  155. package/dist/execution/blob-inputs.d.ts +57 -0
  156. package/dist/execution/bundle-reload.d.ts +42 -0
  157. package/dist/execution/config-resolver.d.ts +127 -0
  158. package/dist/execution/configuration-builder.d.ts +66 -0
  159. package/dist/execution/context-composition.d.ts +161 -0
  160. package/dist/execution/endpoint-actions.d.ts +9 -0
  161. package/dist/execution/endpoint-payloads.d.ts +32 -0
  162. package/dist/execution/endpoint-runner.d.ts +129 -0
  163. package/dist/execution/engine-state.d.ts +40 -0
  164. package/dist/execution/executor-callbacks.d.ts +56 -0
  165. package/dist/execution/executor.d.ts +436 -0
  166. package/dist/execution/gates.d.ts +45 -0
  167. package/dist/execution/hook-chain.d.ts +25 -0
  168. package/dist/execution/logic-calls.d.ts +190 -0
  169. package/dist/execution/logic-failure.d.ts +84 -0
  170. package/dist/execution/logic-loader.d.ts +54 -0
  171. package/dist/execution/logic-result.d.ts +76 -0
  172. package/dist/execution/tick-controller-call.d.ts +32 -0
  173. package/dist/execution/types.d.ts +48 -0
  174. package/dist/generation/ts-type.d.ts +39 -0
  175. package/dist/guide/locale.d.ts +12 -0
  176. package/dist/guide/video-registry.d.ts +42 -0
  177. package/dist/help-examples.d.ts +125 -0
  178. package/dist/host/adapter.d.ts +67 -0
  179. package/dist/host/host-channel.d.ts +222 -0
  180. package/dist/host/index.d.ts +20 -0
  181. package/dist/host/protocol.d.ts +783 -0
  182. package/dist/host/surfaces.d.ts +42 -0
  183. package/dist/host/theme.d.ts +55 -0
  184. package/dist/host.js +583 -0
  185. package/dist/prompts/index.d.ts +7 -0
  186. package/dist/prompts/schema-builder.d.ts +29 -0
  187. package/dist/prompts/types.d.ts +108 -0
  188. package/dist/prompts/validators.d.ts +32 -0
  189. package/dist/runtime/capability-scope.d.ts +6 -0
  190. package/dist/runtime/context-merge.d.ts +29 -0
  191. package/dist/runtime/context-projection.d.ts +41 -0
  192. package/dist/runtime/guarded-callbacks.d.ts +15 -0
  193. package/dist/runtime/index.d.ts +27 -0
  194. package/dist/runtime/manifest-version.d.ts +55 -0
  195. package/dist/runtime/tick-validation.d.ts +25 -0
  196. package/dist/runtime.js +346 -0
  197. package/dist/schema.json +1546 -0
  198. package/dist/server/vite-plugin.d.ts +46 -0
  199. package/dist/services/tts/client.d.ts +51 -0
  200. package/dist/services/tts/elevenlabs.d.ts +24 -0
  201. package/dist/services/tts/types.d.ts +44 -0
  202. package/dist/storage/asset-resolver.d.ts +49 -0
  203. package/dist/storage/blob-store.d.ts +41 -0
  204. package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
  205. package/dist/templates/endpoint/logic.ts.liquid +25 -0
  206. package/dist/templates/engine/init.json.liquid +7 -0
  207. package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
  208. package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
  209. package/dist/templates/engine/logic.test.ts.liquid +61 -0
  210. package/dist/templates/engine/logic.ts.liquid +32 -0
  211. package/dist/templates/engine/run.json.liquid +7 -0
  212. package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
  213. package/dist/templates/guide/02-preparation.md.liquid +18 -0
  214. package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
  215. package/dist/templates/hook/logic.test.ts.liquid +46 -0
  216. package/dist/templates/hook/logic.ts.liquid +12 -0
  217. package/dist/templates/package/CLAUDE.md.template +338 -0
  218. package/dist/templates/package/DESIGN.md.liquid +144 -0
  219. package/dist/templates/package/README.md.liquid +143 -0
  220. package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
  221. package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
  222. package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
  223. package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
  224. package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
  225. package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
  226. package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
  227. package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
  228. package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
  229. package/dist/templates/package/claude/settings.json.template +16 -0
  230. package/dist/templates/package/gitignore.template +15 -0
  231. package/dist/templates/package/global-setup.ts.template +21 -0
  232. package/dist/templates/package/globals.d.ts.template +2 -0
  233. package/dist/templates/package/oxfmtrc.json.template +14 -0
  234. package/dist/templates/package/oxlintrc.json.template +238 -0
  235. package/dist/templates/package/package.json.liquid +65 -0
  236. package/dist/templates/package/playwright.config.ts.liquid +19 -0
  237. package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
  238. package/dist/templates/package/test-setup.ts.liquid +7 -0
  239. package/dist/templates/package/tsconfig.json.liquid +15 -0
  240. package/dist/templates/package/vite.config.ts.liquid +19 -0
  241. package/dist/templates/package/vitest.config.ts.liquid +25 -0
  242. package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
  243. package/dist/templates/presentation/index.html.liquid +20 -0
  244. package/dist/templates/presentation/src/App.tsx.liquid +49 -0
  245. package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
  246. package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
  247. package/dist/templates/presentation/src/main.tsx.liquid +14 -0
  248. package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
  249. package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
  250. package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
  251. package/dist/templates/session/src/App.test.tsx.liquid +60 -0
  252. package/dist/templates/session/src/App.tsx.liquid +128 -0
  253. package/dist/templates/session/src/i18n.ts.liquid +53 -0
  254. package/dist/templates/session/src/main.tsx.liquid +48 -0
  255. package/dist/templates/session/src/theme.ts.liquid +46 -0
  256. package/dist/templates/settings-editor/index.html.liquid +12 -0
  257. package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
  258. package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
  259. package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
  260. package/dist/templates/setup/src/App.tsx.liquid +102 -0
  261. package/dist/templates/setup/src/i18n.ts.liquid +47 -0
  262. package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
  263. package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
  264. package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
  265. package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
  266. package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
  267. package/dist/templates/starter-game/engine/defaults.ts +81 -0
  268. package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
  269. package/dist/templates/starter-game/engine/logic.test.ts +221 -0
  270. package/dist/templates/starter-game/engine/logic.ts +149 -0
  271. package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
  272. package/dist/templates/starter-game/engine/types.ts +139 -0
  273. package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
  274. package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
  275. package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
  276. package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
  277. package/dist/templates/starter-game/guide/guide.test.ts +108 -0
  278. package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
  279. package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
  280. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
  281. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
  282. package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
  283. package/dist/templates/starter-game/package.json +68 -0
  284. package/dist/templates/starter-game/playwright.config.ts +19 -0
  285. package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
  286. package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
  287. package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
  288. package/dist/templates/starter-game/session/defaults.ts +80 -0
  289. package/dist/templates/starter-game/session/index.html +12 -0
  290. package/dist/templates/starter-game/session/session.sdk.json +11 -0
  291. package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
  292. package/dist/templates/starter-game/session/src/App.tsx +309 -0
  293. package/dist/templates/starter-game/session/src/Section.tsx +37 -0
  294. package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
  295. package/dist/templates/starter-game/session/src/format.ts +8 -0
  296. package/dist/templates/starter-game/session/src/i18n.ts +51 -0
  297. package/dist/templates/starter-game/session/src/main.tsx +48 -0
  298. package/dist/templates/starter-game/session/src/theme.ts +45 -0
  299. package/dist/templates/starter-game/session/types.ts +135 -0
  300. package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
  301. package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
  302. package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
  303. package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
  304. package/dist/templates/starter-game/test-setup.ts +7 -0
  305. package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
  306. package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
  307. package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
  308. package/dist/templates/starter-game/tsconfig.json +19 -0
  309. package/dist/templates/starter-game/ui/defaults.ts +60 -0
  310. package/dist/templates/starter-game/ui/index.html +12 -0
  311. package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
  312. package/dist/templates/starter-game/ui/src/App.tsx +175 -0
  313. package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
  314. package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
  315. package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
  316. package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
  317. package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
  318. package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
  319. package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
  320. package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
  321. package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
  322. package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
  323. package/dist/templates/starter-game/ui/src/format.ts +12 -0
  324. package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
  325. package/dist/templates/starter-game/ui/src/main.tsx +48 -0
  326. package/dist/templates/starter-game/ui/src/menu.ts +18 -0
  327. package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
  328. package/dist/templates/starter-game/ui/src/theme.ts +45 -0
  329. package/dist/templates/starter-game/ui/src/writes.ts +43 -0
  330. package/dist/templates/starter-game/ui/types.ts +137 -0
  331. package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
  332. package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
  333. package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
  334. package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
  335. package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
  336. package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
  337. package/dist/templates/starter-game/vite.config.ts +19 -0
  338. package/dist/templates/starter-game/vitest.config.ts +15 -0
  339. package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
  340. package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
  341. package/dist/templates/starter-game/waiting_room/index.html +12 -0
  342. package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
  343. package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
  344. package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
  345. package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
  346. package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
  347. package/dist/templates/starter-game/waiting_room/types.ts +59 -0
  348. package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
  349. package/dist/templates/tick-controller/logic.ts.liquid +8 -0
  350. package/dist/templates/ui/index.html.liquid +12 -0
  351. package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
  352. package/dist/templates/ui/src/App.tsx.liquid +103 -0
  353. package/dist/templates/ui/src/i18n.ts.liquid +47 -0
  354. package/dist/templates/ui/src/main.tsx.liquid +48 -0
  355. package/dist/templates/ui/src/theme.ts.liquid +46 -0
  356. package/dist/templates/validator/logic.test.ts.liquid +42 -0
  357. package/dist/templates/validator/logic.ts.liquid +21 -0
  358. package/dist/templates/waiting-room/index.html.liquid +12 -0
  359. package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
  360. package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
  361. package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
  362. package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
  363. package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
  364. package/dist/templating/resource-metadata.d.ts +131 -0
  365. package/dist/types.d.ts +14 -0
  366. package/dist/utils/atomic-write.d.ts +40 -0
  367. package/dist/utils/cli-error.d.ts +64 -0
  368. package/dist/utils/document-parser-client.d.ts +20 -0
  369. package/dist/utils/env-loader.d.ts +40 -0
  370. package/dist/utils/error-codes.d.ts +339 -0
  371. package/dist/utils/errors.d.ts +19 -0
  372. package/dist/utils/global-settings.d.ts +65 -0
  373. package/dist/utils/llm-client.d.ts +34 -0
  374. package/dist/utils/logger.d.ts +36 -0
  375. package/dist/utils/output.d.ts +109 -0
  376. package/dist/utils/platform.d.ts +14 -0
  377. package/dist/utils/prompt.d.ts +36 -0
  378. package/dist/utils/real-path.d.ts +6 -0
  379. package/dist/utils/schema-properties.d.ts +35 -0
  380. package/dist/validation/ajv.d.ts +17 -0
  381. package/dist/validation/facilitator-guide-validator.d.ts +27 -0
  382. package/dist/validation/findings.d.ts +40 -0
  383. package/dist/validation/game-constraints.d.ts +10 -0
  384. package/dist/validation/game-validator.d.ts +34 -0
  385. package/dist/validation/package-validation.d.ts +41 -0
  386. package/dist/validation/package-validator.d.ts +19 -0
  387. package/dist/validation/resource-validator.d.ts +79 -0
  388. package/dist/validation/schema-defaults-validator.d.ts +29 -0
  389. package/dist/validation/schema-validation.d.ts +115 -0
  390. package/dist/validation/semver.d.ts +9 -0
  391. package/package.json +123 -3
@@ -0,0 +1,102 @@
1
+ import { MutationError } from '@seriousgaming/sdk/ui';
2
+ import {
3
+ createMockParticipant,
4
+ createMockParticipants,
5
+ renderUserInterface,
6
+ } from '@seriousgaming/sdk/ui/testing';
7
+ import { fireEvent, screen, waitFor } from '@testing-library/react';
8
+ import { describe, expect, it, vi } from 'vitest';
9
+
10
+ import type { Context, Settings } from '../types';
11
+ import App from './App';
12
+ import './i18n';
13
+
14
+ const baseContext: Context = {
15
+ roles: { LooksAfter: {} },
16
+ briefing: { Message: '' },
17
+ };
18
+
19
+ const settings: Settings = {
20
+ menu: {
21
+ Capacity: 120,
22
+ Menu: {
23
+ lemonade: { Name: 'Lemonade', Order: 1, Cost: 15, Price: 50 },
24
+ icedtea: { Name: 'Iced tea', Order: 2, Cost: 20, Price: 60 },
25
+ },
26
+ },
27
+ };
28
+
29
+ const ada = createMockParticipant({ id: 'participant-1', name: 'Ada' });
30
+
31
+ const render = (options: Parameters<typeof renderUserInterface>[1] = {}) =>
32
+ renderUserInterface(<App />, { data: baseContext, settings, participant: ada, ...options });
33
+
34
+ describe('before the session starts', () => {
35
+ it('says so, rather than showing an empty stall', () => {
36
+ render({ completedTicks: 0 });
37
+
38
+ expect(screen.getByText('Nearly opening time')).toBeInTheDocument();
39
+ });
40
+
41
+ it('lists the team, marking the reader', () => {
42
+ render({
43
+ participants: createMockParticipants(2),
44
+ participant: createMockParticipant({ id: '1', index: 0 }),
45
+ });
46
+
47
+ expect(screen.getByText(/\(you\)/)).toBeInTheDocument();
48
+ });
49
+
50
+ // The hook stores the item's id; the roster shows its name, so nobody has
51
+ // to know that 'icedtea' means Iced tea.
52
+ it('shows what the hook put each arrival in charge of', () => {
53
+ render({
54
+ data: { ...baseContext, roles: { LooksAfter: { '1': 'icedtea' } } },
55
+ participants: createMockParticipants(1),
56
+ participant: createMockParticipant({ id: '1', index: 0 }),
57
+ });
58
+
59
+ expect(screen.getByText('Iced tea')).toBeInTheDocument();
60
+ });
61
+ });
62
+
63
+ describe('when the facilitator parks a live session', () => {
64
+ // The room can be opened at any point, and the two cases need different
65
+ // words: 'almost ready' is wrong on round five.
66
+ it('says the session is paused rather than not started', () => {
67
+ render({ completedTicks: 4 });
68
+
69
+ expect(screen.getByText('Closed for a moment')).toBeInTheDocument();
70
+ });
71
+
72
+ it('carries the briefing through', () => {
73
+ render({ data: { ...baseContext, briefing: { Message: 'Back in five minutes' } } });
74
+
75
+ expect(screen.getByText('Back in five minutes')).toBeInTheDocument();
76
+ });
77
+ });
78
+
79
+ describe('naming the stall', () => {
80
+ it('asks the host, which owns the name', async () => {
81
+ const renameOwnTeam = vi.fn().mockResolvedValue(undefined);
82
+ render({ callbacks: { renameOwnTeam } });
83
+
84
+ fireEvent.change(screen.getByLabelText('Stall name'), { target: { value: 'Bluebird' } });
85
+ fireEvent.click(screen.getByRole('button', { name: 'Save' }));
86
+
87
+ await waitFor(() => expect(renameOwnTeam).toHaveBeenCalledWith('Bluebird'));
88
+ });
89
+
90
+ it('shows the host’s own reason when a name is refused', async () => {
91
+ const renameOwnTeam = vi
92
+ .fn()
93
+ .mockRejectedValue(
94
+ new MutationError({ kind: 'denied', message: 'A stall name cannot be blank.' })
95
+ );
96
+ render({ callbacks: { renameOwnTeam } });
97
+
98
+ fireEvent.click(screen.getByRole('button', { name: 'Save' }));
99
+
100
+ expect(await screen.findByText('A stall name cannot be blank.')).toBeInTheDocument();
101
+ });
102
+ });
@@ -0,0 +1,194 @@
1
+ import {
2
+ Box,
3
+ Button,
4
+ Chip,
5
+ CssBaseline,
6
+ Divider,
7
+ Stack,
8
+ TextField,
9
+ ThemeProvider,
10
+ Typography,
11
+ } from '@mui/material';
12
+ import {
13
+ MutationError,
14
+ useUserInterfaceOwnTeam,
15
+ useUserInterfaceOwnTeamJoinLink,
16
+ useUserInterfaceCallbacks,
17
+ useUserInterfaceData,
18
+ useUserInterfaceParticipant,
19
+ useUserInterfaceParticipants,
20
+ } from '@seriousgaming/sdk/ui';
21
+ import { useState } from 'react';
22
+ import { useTranslation } from 'react-i18next';
23
+
24
+ import type { Settings, Context } from '../types';
25
+ import { useAppTheme } from './theme';
26
+
27
+ /**
28
+ * The holding screen.
29
+ *
30
+ * A waiting room is a player surface shown somewhere else, not a reduced one:
31
+ * the same hooks, the same callbacks, the same protocol. That is why there is
32
+ * no waiting-room-specific API anywhere in this file.
33
+ *
34
+ * Two things are worth knowing while you build one:
35
+ *
36
+ * 1. The facilitator can open it at any point, not only before the session
37
+ * starts. `completedTicks` tells the two apart: 0 means no day has been
38
+ * played, anything higher means a live session has been parked
39
+ * 2. Opening it does not stop the clock. A game that wants automatic ticks to
40
+ * stop while people wait reads `waitingRoomOpen` in its tick controller and
41
+ * returns `enabled: false`. This game is facilitator-paced, so there is
42
+ * nothing to stop
43
+ */
44
+ function Shell() {
45
+ const { data, settings, completedTicks } = useUserInterfaceData<Context, Settings>();
46
+ const { t } = useTranslation();
47
+ const ownTeam = useUserInterfaceOwnTeam();
48
+ const joinLink = useUserInterfaceOwnTeamJoinLink();
49
+ const { renameOwnTeam } = useUserInterfaceCallbacks();
50
+ const participants = useUserInterfaceParticipants();
51
+ const self = useUserInterfaceParticipant();
52
+
53
+ const [draftName, setDraftName] = useState('');
54
+ // The host's own reason, not a flag of ours. It knows whether the name was
55
+ // blank or too long, and a fixed 'try a shorter one' is wrong half the time.
56
+ const [refusal, setRefusal] = useState<string | null>(null);
57
+
58
+ const briefing = data.briefing.Message;
59
+ const looksAfter = data.roles.LooksAfter;
60
+ const roster = [...participants].sort((a, b) => a.index - b.index);
61
+
62
+ return (
63
+ <Box
64
+ sx={{
65
+ display: 'flex',
66
+ flexDirection: 'column',
67
+ alignItems: 'center',
68
+ justifyContent: 'center',
69
+ gap: 2.5,
70
+ minHeight: '100vh',
71
+ p: 4,
72
+ textAlign: 'center',
73
+ }}
74
+ >
75
+ <Typography variant="h4" component="h1">
76
+ {completedTicks === 0 ? t('waiting.beforeStart') : t('waiting.paused')}
77
+ </Typography>
78
+
79
+ <Typography variant="body1" color="text.secondary" sx={{ maxWidth: 520 }}>
80
+ {completedTicks === 0 ? t('waiting.beforeStartHint') : t('waiting.pausedHint')}
81
+ </Typography>
82
+
83
+ {briefing !== '' && (
84
+ <Typography variant="body2" sx={{ fontStyle: 'italic', maxWidth: 520 }}>
85
+ {briefing}
86
+ </Typography>
87
+ )}
88
+
89
+ <Divider flexItem sx={{ maxWidth: 360, alignSelf: 'center' }} />
90
+
91
+ <Typography variant="subtitle1">
92
+ {ownTeam ? t('waiting.youAre', { team: ownTeam.name }) : t('waiting.noTeamYet')}
93
+ </Typography>
94
+
95
+ {/* The team names itself here, and that name is the team's name
96
+ everywhere, including the host's own chrome. So this renders
97
+ `ownTeam.name` rather than what it just sent: the facilitator can
98
+ be renaming the same team at the same moment. */}
99
+ <Stack direction="row" spacing={1} sx={{ alignItems: 'flex-start' }}>
100
+ <TextField
101
+ size="small"
102
+ label={t('waiting.namePrompt')}
103
+ value={draftName}
104
+ error={refusal !== null}
105
+ helperText={refusal ?? undefined}
106
+ onChange={(event) => {
107
+ setDraftName(event.target.value);
108
+ setRefusal(null);
109
+ }}
110
+ />
111
+ <Button
112
+ variant="contained"
113
+ onClick={async () => {
114
+ try {
115
+ await renameOwnTeam(draftName);
116
+ setDraftName('');
117
+ } catch (error) {
118
+ setRefusal(
119
+ error instanceof MutationError
120
+ ? error.message
121
+ : t('waiting.nameFailed')
122
+ );
123
+ }
124
+ }}
125
+ >
126
+ {t('waiting.nameSave')}
127
+ </Button>
128
+ </Stack>
129
+
130
+ <Box component="ul" sx={{ listStyle: 'none', p: 0, m: 0, display: 'grid', gap: 1 }}>
131
+ {roster.map((participant) => (
132
+ <Box
133
+ component="li"
134
+ key={participant.id}
135
+ sx={{
136
+ display: 'flex',
137
+ alignItems: 'center',
138
+ justifyContent: 'center',
139
+ gap: 1,
140
+ }}
141
+ >
142
+ <Box
143
+ aria-hidden
144
+ sx={{
145
+ width: 8,
146
+ height: 8,
147
+ borderRadius: '50%',
148
+ bgcolor: participant.connected ? 'success.main' : 'text.disabled',
149
+ }}
150
+ />
151
+ <Typography variant="body2">
152
+ {participant.id === self?.id
153
+ ? t('waiting.you', { name: participant.name })
154
+ : participant.name}
155
+ </Typography>
156
+ {looksAfter[participant.id] && (
157
+ <Chip
158
+ size="small"
159
+ label={
160
+ settings.menu.Menu[looksAfter[participant.id] as string]
161
+ ?.Name ?? looksAfter[participant.id]
162
+ }
163
+ />
164
+ )}
165
+ </Box>
166
+ ))}
167
+ </Box>
168
+
169
+ {/* Every player surface receives its own team's join link, so a
170
+ latecomer joins the team already in front of them rather than
171
+ waiting for somebody to read a URL out. Null until the host
172
+ delivers it, so the panel is absent rather than empty. */}
173
+ {joinLink && (
174
+ <Box sx={{ display: 'grid', gap: 0.5 }}>
175
+ <Typography variant="subtitle2">{t('waiting.joinHeading')}</Typography>
176
+ <Typography variant="body2" sx={{ fontFamily: 'monospace' }}>
177
+ {joinLink.joinUrl}
178
+ </Typography>
179
+ </Box>
180
+ )}
181
+ </Box>
182
+ );
183
+ }
184
+
185
+ export default function App() {
186
+ const theme = useAppTheme();
187
+
188
+ return (
189
+ <ThemeProvider theme={theme}>
190
+ <CssBaseline enableColorScheme />
191
+ <Shell />
192
+ </ThemeProvider>
193
+ );
194
+ }
@@ -0,0 +1,33 @@
1
+ import i18n from 'i18next';
2
+ import { initReactI18next } from 'react-i18next';
3
+
4
+ const resources = {
5
+ en: {
6
+ translation: {
7
+ waiting: {
8
+ beforeStart: 'Nearly opening time',
9
+ beforeStartHint:
10
+ 'Give your stall a name while you wait. The facilitator will open up when everyone is here.',
11
+ paused: 'Closed for a moment',
12
+ pausedHint:
13
+ 'Your stall is exactly as you left it. You will be back on your own screen shortly.',
14
+ youAre: 'You are on {{team}}',
15
+ noTeamYet: 'Waiting for the facilitator to put you on a stall',
16
+ namePrompt: 'Stall name',
17
+ nameSave: 'Save',
18
+ nameFailed: 'That name was not accepted.',
19
+ you: '{{name}} (you)',
20
+ joinHeading: 'Anyone else joining this stall',
21
+ },
22
+ },
23
+ },
24
+ };
25
+
26
+ i18n.use(initReactI18next).init({
27
+ resources,
28
+ lng: 'en',
29
+ fallbackLng: 'en',
30
+ interpolation: { escapeValue: false },
31
+ });
32
+
33
+ export default i18n;
@@ -0,0 +1,48 @@
1
+ import createCache from '@emotion/cache';
2
+ import { CacheProvider } from '@emotion/react';
3
+ import '@fontsource-variable/roboto-mono';
4
+ import '@fontsource-variable/work-sans';
5
+ import rtlPlugin from '@mui/stylis-plugin-rtl';
6
+ import { UserInterfaceWrapper, useUserInterfaceLocale } from '@seriousgaming/sdk/ui';
7
+
8
+ import './i18n';
9
+ import i18n from 'i18next';
10
+ import React, { StrictMode, useEffect } from 'react';
11
+ import { createRoot } from 'react-dom/client';
12
+ import { prefixer } from 'stylis';
13
+
14
+ import App from './App';
15
+
16
+ // Create RTL cache
17
+ const cacheRtl = createCache({
18
+ key: 'muirtl',
19
+ stylisPlugins: [prefixer, rtlPlugin],
20
+ });
21
+
22
+ const cacheLtr = createCache({
23
+ key: 'muiltr',
24
+ });
25
+
26
+ // Wrapper component to sync locale with i18next and setup RTL
27
+ function RootWrapper({ children }: { children: React.ReactNode }) {
28
+ const { locale } = useUserInterfaceLocale();
29
+
30
+ useEffect(() => {
31
+ const i18nextLocale = locale.split('-')[0];
32
+ i18n.changeLanguage(i18nextLocale);
33
+ }, [locale]);
34
+
35
+ const isRtl = i18n.dir(locale.split('-')[0]) === 'rtl';
36
+
37
+ return <CacheProvider value={isRtl ? cacheRtl : cacheLtr}>{children}</CacheProvider>;
38
+ }
39
+
40
+ createRoot(document.getElementById('root')!).render(
41
+ <StrictMode>
42
+ <UserInterfaceWrapper>
43
+ <RootWrapper>
44
+ <App />
45
+ </RootWrapper>
46
+ </UserInterfaceWrapper>
47
+ </StrictMode>
48
+ );
@@ -0,0 +1,45 @@
1
+ import { createTheme } from '@mui/material/styles';
2
+ import { useUserInterfaceTheme } from '@seriousgaming/sdk/ui';
3
+ import { useMemo } from 'react';
4
+
5
+ /**
6
+ * The host owns the palette, the game owns the typography and the component
7
+ * shapes. Reading `theme.mode` rather than the browser's preference is what
8
+ * keeps a participant's screen and the facilitator's looking like one product.
9
+ */
10
+ export const useAppTheme = () => {
11
+ const theme = useUserInterfaceTheme();
12
+
13
+ return useMemo(() => {
14
+ const colors = theme.mode === 'light' ? theme.light : theme.dark;
15
+ return createTheme({
16
+ palette: {
17
+ mode: theme.mode,
18
+ primary: { main: colors.primary, contrastText: colors.contrastText.primary },
19
+ secondary: { main: colors.secondary, contrastText: colors.contrastText.secondary },
20
+ success: { main: colors.success, contrastText: colors.contrastText.success },
21
+ error: { main: colors.error, contrastText: colors.contrastText.error },
22
+ warning: { main: colors.warning, contrastText: colors.contrastText.warning },
23
+ info: { main: colors.info, contrastText: colors.contrastText.info },
24
+ },
25
+ shape: { borderRadius: 10 },
26
+ components: {
27
+ MuiButton: { styleOverrides: { root: { textTransform: 'none' } } },
28
+ MuiTab: {
29
+ styleOverrides: { root: { textTransform: 'none', fontSize: '0.95rem' } },
30
+ },
31
+ MuiToggleButton: { styleOverrides: { root: { textTransform: 'none' } } },
32
+ MuiTableCell: {
33
+ styleOverrides: {
34
+ head: {
35
+ fontSize: '0.75rem',
36
+ fontWeight: 700,
37
+ letterSpacing: '0.07em',
38
+ textTransform: 'uppercase',
39
+ },
40
+ },
41
+ },
42
+ },
43
+ });
44
+ }, [theme]);
45
+ };
@@ -0,0 +1,59 @@
1
+ // This file is auto-generated from the waiting room user interface's capability definitions.
2
+ // Do not edit this file directly. It will be regenerated on build.
3
+
4
+ export interface Context {
5
+ roles: {
6
+ /**
7
+ * Which menu item each participant is in charge of, keyed by participant id. Written by the participantJoin hook, and the reason two people normally edit two different keys.
8
+ */
9
+ LooksAfter: {
10
+ [k: string]: string;
11
+ };
12
+ };
13
+ briefing: {
14
+ /**
15
+ * Facilitator note shown on every team's screen. Shared scope: one value for the whole session, written only from the session surface. Session lifetime: an undo or a reset leaves it as the facilitator last wrote it.
16
+ */
17
+ Message: string;
18
+ };
19
+ }
20
+
21
+ export type CrossTeamContext = Record<string, never>;
22
+
23
+ export interface Settings {
24
+ menu: {
25
+ /**
26
+ * How many items the stall can make in a day, across the whole menu
27
+ */
28
+ Capacity: number;
29
+ Menu: Menu;
30
+ };
31
+ }
32
+ /**
33
+ * What the stall sells, keyed by item id. Money is in pence, so the sums stay in whole numbers. Read by the engine and every surface, so the price on screen and the price in the sums are the same number.
34
+ */
35
+ export interface Menu {
36
+ [k: string]: MenuItem;
37
+ }
38
+ export interface MenuItem {
39
+ /**
40
+ * What the item is called on every screen
41
+ */
42
+ Name: string;
43
+ /**
44
+ * Where it sits on the menu, so every screen lists it in the same order. 1 comes first
45
+ */
46
+ Order: number;
47
+ /**
48
+ * Cost to make one, in pence
49
+ */
50
+ Cost: number;
51
+ /**
52
+ * Sale price, in pence
53
+ */
54
+ Price: number;
55
+ }
56
+
57
+ import type { WaitingRoomUserInterfaceCallbacks } from '@seriousgaming/sdk';
58
+
59
+ export type Callbacks = Pick<WaitingRoomUserInterfaceCallbacks, never>;
@@ -0,0 +1,10 @@
1
+ {
2
+ "definition": {
3
+ "reads": ["roles", "briefing"],
4
+ "settings": ["menu"]
5
+ },
6
+ "ui": {
7
+ "entrypoint": "index.html"
8
+ },
9
+ "rootDirectory": "."
10
+ }
@@ -0,0 +1,8 @@
1
+ import { GetTickControllerFunction, intervalTick } from '@seriousgaming/sdk';
2
+ import type { Context, Settings, Callbacks } from './types';
3
+
4
+ export const GetTickController: GetTickControllerFunction<Context, Settings, Callbacks> = async ({ context, settings, callbacks }) => {
5
+ // `teams` (always present) lists every team this call covers as { teamId, index, context }.
6
+ // intervalTick takes milliseconds and validates the cadence against the 30s–7d bounds.
7
+ return intervalTick(60_000);
8
+ };
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{ name }}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,41 @@
1
+ {% raw %}import { describe, it, expect, vi } from 'vitest';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import {
4
+ UserInterfaceThemeContext,
5
+ UserInterfaceDataContext,
6
+ } from '@seriousgaming/sdk/ui';
7
+ import { createMockTheme } from '@seriousgaming/sdk/ui/testing';
8
+ import './i18n';
9
+ import App from './App';
10
+
11
+ // Wrap App with the SDK context providers it needs.
12
+ // These replace the iframe communication that the real app uses — no mocking needed.
13
+ const renderApp = (data = {}, settings = {}) =>
14
+ render(
15
+ <UserInterfaceThemeContext.Provider value={createMockTheme()}>
16
+ <UserInterfaceDataContext.Provider
17
+ value={{ data, settings, completedTicks: 0, isRoundProcessing: false, setData: vi.fn() }}
18
+ >
19
+ <App />
20
+ </UserInterfaceDataContext.Provider>
21
+ </UserInterfaceThemeContext.Provider>
22
+ );
23
+
24
+ describe('App', () => {
25
+ it('renders the welcome message', () => {
26
+ renderApp();
27
+ expect(screen.getByText(/welcome/i)).toBeInTheDocument();
28
+ });
29
+
30
+ it('increments the counter on button click', () => {
31
+ renderApp();
32
+
33
+ expect(screen.getByText(/current value: 0/i)).toBeInTheDocument();
34
+
35
+ // The counter uses local state, so userEvent is appropriate here.
36
+ // For controlled inputs that call setData, use fireEvent.change instead.
37
+ fireEvent.click(screen.getByRole('button', { name: /increment/i }));
38
+
39
+ expect(screen.getByText(/current value: 1/i)).toBeInTheDocument();
40
+ });
41
+ });{% endraw %}
@@ -0,0 +1,103 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ Box,
4
+ Button,
5
+ CssBaseline,
6
+ ThemeProvider,
7
+ Typography,
8
+ } from '@mui/material';
9
+ import { useUserInterfaceData } from '@seriousgaming/sdk/ui';
10
+ import { useTranslation } from 'react-i18next';
11
+ import { Context, Settings } from '../types';
12
+ import { useAppTheme } from './theme';
13
+
14
+ /*
15
+ This UI requires the following dependencies:
16
+ - @mui/material
17
+ - @emotion/react
18
+ - @emotion/styled
19
+ - stylis
20
+ - @mui/stylis-plugin-rtl
21
+ - @fontsource-variable/roboto-mono
22
+ - @fontsource-variable/work-sans
23
+ - i18next
24
+ - react-i18next
25
+
26
+ Make sure to install them in your project, e.g.:
27
+ pnpm add @mui/material @emotion/react @emotion/styled stylis @mui/stylis-plugin-rtl @fontsource-variable/roboto-mono @fontsource-variable/work-sans i18next react-i18next
28
+ */
29
+
30
+ /*
31
+ This template includes an example of a counter that reads and writes data.
32
+ By default, it uses local component state. To connect it to the game's
33
+ shared state, follow these steps:
34
+
35
+ 1. Create a capability for the counter:
36
+ pnpm run sdk new capability '{
37
+ "definition": {
38
+ "identifier": "uicounter",
39
+ "variablesSchema": {
40
+ "type": "object",
41
+ "properties": {
42
+ "value": { "type": "number", "default": 0 }
43
+ },
44
+ "required": ["value"],
45
+ "additionalProperties": false
46
+ }
47
+ }
48
+ }'
49
+
50
+ 2. In your ui.sdk.json, add "uicounter" to the `writes` array.
51
+
52
+ 3. In this file (App.tsx), uncomment the lines related to `useUserInterfaceData`
53
+ to connect the component to the capability.
54
+ */
55
+
56
+ function App() {
57
+ // const { data, setData } = useUserInterfaceData<Context, Settings>();
58
+ const { t } = useTranslation();
59
+ const theme = useAppTheme();
60
+ const [localCounter, setLocalCounter] = useState(0);
61
+
62
+ // This is a local state example.
63
+ // To connect to game data, comment out the local state above and
64
+ // uncomment the `useUserInterfaceData` hook and the `counterValue` line below.
65
+ const counterValue = localCounter;
66
+ // const counterValue = data.uicounter.value;
67
+
68
+ const handleIncrement = () => {
69
+ // This is a local state update example.
70
+ setLocalCounter((c) => c + 1);
71
+
72
+ // To send data to the game, uncomment the `setData` call below.
73
+ // setData({
74
+ // uicounter: {
75
+ // // Spread existing capability data to avoid overwriting other variables
76
+ // ...(data.uicounter || {}),
77
+ // value: (data.uicounter?.value ?? 0) + 1,
78
+ // },
79
+ // });
80
+ };
81
+
82
+ return (
83
+ <ThemeProvider theme={theme}>
84
+ <CssBaseline enableColorScheme />
85
+ <Box>
86
+ <Typography variant="body1">{t('welcomeMessage')}</Typography>
87
+ <Typography variant="body2" color="text.secondary">
88
+ {t('dataExample')}
89
+ </Typography>
90
+ <Typography variant="h6" component="p">
91
+ {t('currentValue', { value: counterValue })}
92
+ </Typography>
93
+ <Box>
94
+ <Button variant="contained" onClick={handleIncrement}>
95
+ {t('updateButton')}
96
+ </Button>
97
+ </Box>
98
+ </Box>
99
+ </ThemeProvider>
100
+ );
101
+ }
102
+
103
+ export default App;