@seriousgaming/sdk-tools 0.0.0-stage → 1.0.0-alpha.1

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 +36 -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 +27368 -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 +16884 -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 +209 -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 +236 -0
  69. package/dist/docs/cli-and-dev-server.md +217 -0
  70. package/dist/docs/cli-error-codes.md +154 -0
  71. package/dist/docs/cli-reference.md +2488 -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 +575 -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 +335 -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 +122 -3
@@ -0,0 +1,37 @@
1
+ import { Box, Card, CardContent, Stack, Typography } from '@mui/material';
2
+ import type { ReactNode } from 'react';
3
+
4
+ type Props = {
5
+ title: string;
6
+ hint?: string;
7
+ action?: ReactNode;
8
+ children: ReactNode;
9
+ };
10
+
11
+ /** One card, one heading, one job. Every panel on this surface is built of these. */
12
+ export default function Section({ title, hint, action, children }: Props) {
13
+ return (
14
+ <Card variant="outlined">
15
+ <CardContent>
16
+ <Stack
17
+ direction="row"
18
+ spacing={2}
19
+ sx={{ alignItems: 'flex-start', justifyContent: 'space-between' }}
20
+ >
21
+ <Box>
22
+ <Typography variant="h6" component="h2">
23
+ {title}
24
+ </Typography>
25
+ {hint && (
26
+ <Typography variant="body2" color="text.secondary">
27
+ {hint}
28
+ </Typography>
29
+ )}
30
+ </Box>
31
+ {action}
32
+ </Stack>
33
+ <Box sx={{ mt: 2 }}>{children}</Box>
34
+ </CardContent>
35
+ </Card>
36
+ );
37
+ }
@@ -0,0 +1,49 @@
1
+ import { ToggleButton, ToggleButtonGroup } from '@mui/material';
2
+ import { useUserInterfaceCallbacks, useUserInterfaceThemePreference } from '@seriousgaming/sdk/ui';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ const OPTIONS = [
6
+ { value: 'light', glyph: '☀' },
7
+ { value: 'dark', glyph: '☾' },
8
+ { value: 'system', glyph: 'A' },
9
+ ] as const;
10
+
11
+ /**
12
+ * Light, dark, or whatever the device says.
13
+ *
14
+ * The preference is the host's, not this surface's: `setThemePreference` tells
15
+ * the host, the host decides, and the new palette comes back through
16
+ * `useUserInterfaceTheme` like any other state. So the whole session follows,
17
+ * including the facilitator's screen and the chrome around this frame, rather
18
+ * than one iframe going dark on its own.
19
+ */
20
+ export default function ThemeToggle() {
21
+ const preference = useUserInterfaceThemePreference();
22
+ const { setThemePreference } = useUserInterfaceCallbacks();
23
+ const { t } = useTranslation();
24
+
25
+ return (
26
+ <ToggleButtonGroup
27
+ size="small"
28
+ exclusive
29
+ value={preference}
30
+ aria-label={t('theme.label')}
31
+ onChange={(_, next) => {
32
+ // Null when the pressed button was already selected. Keeping the
33
+ // current value means the control never lands in no state at all.
34
+ if (next) setThemePreference(next);
35
+ }}
36
+ >
37
+ {OPTIONS.map((option) => (
38
+ <ToggleButton
39
+ key={option.value}
40
+ value={option.value}
41
+ aria-label={t(`theme.${option.value}`)}
42
+ sx={{ px: 1.25, py: 0.25, lineHeight: 1 }}
43
+ >
44
+ {option.glyph}
45
+ </ToggleButton>
46
+ ))}
47
+ </ToggleButtonGroup>
48
+ );
49
+ }
@@ -0,0 +1,179 @@
1
+ import {
2
+ createMockParticipant,
3
+ mergingSetData,
4
+ renderUserInterface,
5
+ } from '@seriousgaming/sdk/ui/testing';
6
+ import { fireEvent, screen } from '@testing-library/react';
7
+ import { describe, expect, it, vi } from 'vitest';
8
+
9
+ import type { Context } from '../types';
10
+ import './i18n';
11
+ import { baseSettings, baseContext } from './test-helpers';
12
+ import TodayPanel from './TodayPanel';
13
+
14
+ const ada = createMockParticipant({ id: 'participant-1', name: 'Ada' });
15
+
16
+ const setSlider = (name: string, value: number) => {
17
+ const slider = screen.getByRole('slider', { name });
18
+ fireEvent.change(slider, { target: { value: String(value) } });
19
+ // The row writes on release, not on every frame of a drag.
20
+ fireEvent.mouseUp(slider);
21
+ };
22
+
23
+ describe('changing how many to make', () => {
24
+ it('sends only the item that changed', () => {
25
+ const setData = vi.fn();
26
+ renderUserInterface(<TodayPanel />, {
27
+ data: baseContext,
28
+ settings: baseSettings,
29
+ participant: ada,
30
+ setData,
31
+ });
32
+
33
+ fireEvent.click(screen.getByRole('button', { name: 'Make more Iced tea' }));
34
+
35
+ // Only `icedtea`. Never the whole `Make` map: that would re-send the
36
+ // lemonade and the cookies from a snapshot that may already be stale.
37
+ expect(setData).toHaveBeenCalledWith({ plan: { Make: { icedtea: 25 } } });
38
+ });
39
+
40
+ it('nudges by the configured step', () => {
41
+ const setData = vi.fn();
42
+ renderUserInterface(<TodayPanel />, {
43
+ data: baseContext,
44
+ settings: { ...baseSettings, ui: { ...baseSettings.ui, Step: 1 } },
45
+ participant: ada,
46
+ setData,
47
+ });
48
+
49
+ fireEvent.click(screen.getByRole('button', { name: 'Make less Lemonade' }));
50
+
51
+ expect(setData).toHaveBeenCalledWith({ plan: { Make: { lemonade: 39 } } });
52
+ });
53
+
54
+ /**
55
+ * The whole reason the plan is a map keyed by item rather than three fields
56
+ * on one object. This harness merges the way the host does, so a payload
57
+ * that reached too wide would show up here as a team-mate's number being
58
+ * undone.
59
+ */
60
+ it('leaves the items somebody else is looking after alone', () => {
61
+ let data: Context = baseContext;
62
+ const setData = mergingSetData<Context>((update) => {
63
+ data = update(data);
64
+ });
65
+
66
+ renderUserInterface(<TodayPanel />, {
67
+ data,
68
+ settings: baseSettings,
69
+ participant: ada,
70
+ setData,
71
+ });
72
+
73
+ // Grace has just taken the cookies to 30, and it has landed.
74
+ data = { ...data, plan: { Make: { ...data.plan.Make, cookies: 30 } } };
75
+
76
+ // Ada, who never saw that, changes her own item.
77
+ setSlider('Lemonade', 55);
78
+
79
+ expect(data.plan.Make).toEqual({ lemonade: 55, icedtea: 20, cookies: 30 });
80
+ });
81
+
82
+ it('writes the number the slider was left on', () => {
83
+ const setData = vi.fn();
84
+ renderUserInterface(<TodayPanel />, {
85
+ data: baseContext,
86
+ settings: baseSettings,
87
+ participant: ada,
88
+ setData,
89
+ });
90
+
91
+ setSlider('Lemonade', 70);
92
+
93
+ expect(setData).toHaveBeenLastCalledWith({ plan: { Make: { lemonade: 70 } } });
94
+ });
95
+ });
96
+
97
+ describe('the stall’s capacity', () => {
98
+ it('says how much room is left', () => {
99
+ renderUserInterface(<TodayPanel />, {
100
+ data: baseContext,
101
+ settings: baseSettings,
102
+ participant: ada,
103
+ });
104
+
105
+ expect(screen.getByTestId('capacity')).toHaveTextContent('70 of 120');
106
+ expect(screen.getByText(/Room for 50 more/)).toBeInTheDocument();
107
+ });
108
+
109
+ // A merged plan can exceed the stall even though no one write did, so the
110
+ // surface has to be able to render that state rather than assume it away.
111
+ it('says so when the plan is over what the stall can make', () => {
112
+ renderUserInterface(<TodayPanel />, {
113
+ data: {
114
+ ...baseContext,
115
+ plan: { Make: { lemonade: 90, icedtea: 40, cookies: 20 } },
116
+ },
117
+ settings: baseSettings,
118
+ participant: ada,
119
+ });
120
+
121
+ expect(screen.getByTestId('capacity')).toHaveTextContent('150 of 120');
122
+ expect(screen.getByText(/30 more than the stall can make/)).toBeInTheDocument();
123
+ });
124
+ });
125
+
126
+ describe('the menu', () => {
127
+ it('lists in the order the game gave, not whatever order the keys arrived in', () => {
128
+ renderUserInterface(<TodayPanel />, {
129
+ data: baseContext,
130
+ settings: baseSettings,
131
+ participant: ada,
132
+ });
133
+
134
+ const names = screen
135
+ .getAllByRole('slider')
136
+ .map((slider) => slider.getAttribute('aria-label'));
137
+ expect(names).toEqual(['Lemonade', 'Iced tea', 'Cookies']);
138
+ });
139
+
140
+ it('marks the item this participant looks after', () => {
141
+ renderUserInterface(<TodayPanel />, {
142
+ data: {
143
+ ...baseContext,
144
+ roles: { LooksAfter: { 'participant-1': 'cookies' } },
145
+ },
146
+ settings: baseSettings,
147
+ participant: ada,
148
+ });
149
+
150
+ expect(screen.getByTestId('make-cookies')).toHaveTextContent('Yours');
151
+ expect(screen.getByTestId('make-lemonade')).not.toHaveTextContent('Yours');
152
+ });
153
+
154
+ it('shows what each one earns and costs, and what today looks like', () => {
155
+ renderUserInterface(<TodayPanel />, {
156
+ data: baseContext,
157
+ settings: baseSettings,
158
+ participant: ada,
159
+ });
160
+
161
+ expect(screen.getByTestId('make-lemonade')).toHaveTextContent(
162
+ '£0.50 each, costs £0.15 to make. About 72 people will want one today.'
163
+ );
164
+ });
165
+ });
166
+
167
+ describe('observing', () => {
168
+ it('says so, and offers nothing to change', () => {
169
+ renderUserInterface(<TodayPanel />, {
170
+ data: baseContext,
171
+ settings: baseSettings,
172
+ viewerRole: 'observer',
173
+ });
174
+
175
+ expect(screen.getByText(/watching this stall/)).toBeInTheDocument();
176
+ expect(screen.getByRole('slider', { name: 'Lemonade' })).toBeDisabled();
177
+ expect(screen.getByRole('button', { name: 'Make more Lemonade' })).toBeDisabled();
178
+ });
179
+ });
@@ -0,0 +1,92 @@
1
+ import { Alert, Chip, LinearProgress, Stack, Typography } from '@mui/material';
2
+ import { useUserInterfaceData, useUserInterfaceParticipant } from '@seriousgaming/sdk/ui';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ import type { Settings, Context } from '../types';
6
+ import MakeRow from './MakeRow';
7
+ import { menuOf, totalPlanned } from './menu';
8
+ import Section from './Section';
9
+ import { useWrite } from './writes';
10
+
11
+ /**
12
+ * What the stall is making today.
13
+ *
14
+ * The only screen that writes, and it writes one key per line of the menu. Two
15
+ * people changing two different things write two different keys, and the host's
16
+ * merge keeps both: no locks, no turn-taking, and nothing to explain to the
17
+ * room. The capacity they share is the one thing that can still go wrong, and
18
+ * `validators/capacity` is what catches it.
19
+ */
20
+ export default function TodayPanel() {
21
+ const { data, settings, setData } = useUserInterfaceData<Context, Settings>();
22
+ const participant = useUserInterfaceParticipant();
23
+ const { run, pending } = useWrite();
24
+ const { t } = useTranslation();
25
+
26
+ const menu = menuOf(settings.menu);
27
+ const planned = totalPlanned(data.plan.Make);
28
+ const room = settings.menu.Capacity - planned;
29
+ const yours = participant ? data.roles.LooksAfter[participant.id] : undefined;
30
+
31
+ const setCount = (id: string, next: number) => {
32
+ // Only this item's key. Never `{ ...data.plan.Make, [id]: next }`: that
33
+ // re-sends every other item from a snapshot that may already be stale,
34
+ // and would undo whatever a team-mate changed a moment ago.
35
+ void run(() => setData({ plan: { Make: { [id]: next } } }), `make:${id}`);
36
+ };
37
+
38
+ return (
39
+ <Section
40
+ title={t('today.title')}
41
+ hint={t(`weather.${data.weather.Today}`)}
42
+ action={
43
+ <Chip
44
+ color={room < 0 ? 'error' : room === 0 ? 'success' : 'default'}
45
+ label={t('today.capacity', { planned, capacity: settings.menu.Capacity })}
46
+ data-testid="capacity"
47
+ />
48
+ }
49
+ >
50
+ <LinearProgress
51
+ variant="determinate"
52
+ color={room < 0 ? 'error' : 'primary'}
53
+ value={Math.min(100, (planned / Math.max(1, settings.menu.Capacity)) * 100)}
54
+ sx={{ mb: 3 }}
55
+ />
56
+
57
+ {!participant && (
58
+ <Alert severity="info" sx={{ mb: 3 }}>
59
+ {t('today.observing')}
60
+ </Alert>
61
+ )}
62
+
63
+ <Stack spacing={3}>
64
+ {menu.map(({ id, item }) => (
65
+ <MakeRow
66
+ key={id}
67
+ id={id}
68
+ item={item}
69
+ value={data.plan.Make[id] ?? 0}
70
+ expected={data.weather.Expected[id] ?? 0}
71
+ // One item can fill the stall on its own, so the cap on
72
+ // a single row is the stall's. The rule about the total
73
+ // is the validator's, because only the merged plan
74
+ // shows it.
75
+ max={settings.menu.Capacity}
76
+ step={settings.ui.Step}
77
+ currencySymbol={settings.ui.CurrencySymbol}
78
+ yours={yours === id}
79
+ disabled={!participant || pending}
80
+ onChange={(next) => setCount(id, next)}
81
+ />
82
+ ))}
83
+ </Stack>
84
+
85
+ <Typography variant="body2" color="text.secondary" sx={{ mt: 3 }}>
86
+ {room < 0
87
+ ? t('today.over', { over: -room })
88
+ : t('today.room', { room, capacity: settings.menu.Capacity })}
89
+ </Typography>
90
+ </Section>
91
+ );
92
+ }
@@ -0,0 +1,142 @@
1
+ import {
2
+ Alert,
3
+ Box,
4
+ Stack,
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableRow,
10
+ Typography,
11
+ } from '@mui/material';
12
+ import { useUserInterfaceData } from '@seriousgaming/sdk/ui';
13
+ import { useTranslation } from 'react-i18next';
14
+
15
+ import type { Settings, Context } from '../types';
16
+ import { count, money } from './format';
17
+ import { menuOf } from './menu';
18
+ import Section from './Section';
19
+
20
+ function Stat({
21
+ id,
22
+ label,
23
+ value,
24
+ tone,
25
+ }: {
26
+ id: string;
27
+ label: string;
28
+ value: string;
29
+ tone?: 'good' | 'bad';
30
+ }) {
31
+ return (
32
+ <Box sx={{ minWidth: 110 }} data-testid={`stat-${id}`}>
33
+ <Typography variant="overline" color="text.secondary" component="p">
34
+ {label}
35
+ </Typography>
36
+ <Typography
37
+ variant="h6"
38
+ component="p"
39
+ sx={{ fontVariantNumeric: 'tabular-nums' }}
40
+ color={tone === 'good' ? 'success.main' : tone === 'bad' ? 'error.main' : undefined}
41
+ >
42
+ {value}
43
+ </Typography>
44
+ </Box>
45
+ );
46
+ }
47
+
48
+ /** How the last day actually went, item by item. */
49
+ export default function YesterdayPanel() {
50
+ const { data, settings } = useUserInterfaceData<Context, Settings>();
51
+ const { t } = useTranslation();
52
+ const symbol = settings.ui.CurrencySymbol;
53
+ const results = data.results;
54
+
55
+ if (results.Day === 0) {
56
+ return (
57
+ <Section title={t('yesterday.title')} hint={t('yesterday.none')}>
58
+ <Alert severity="info">{t('yesterday.firstDay')}</Alert>
59
+ </Section>
60
+ );
61
+ }
62
+
63
+ const pouredAway = Object.values(results.Lines).reduce((sum, l) => sum + l.PouredAway, 0);
64
+ const turnedAway = Object.values(results.Lines).reduce((sum, l) => sum + l.TurnedAway, 0);
65
+
66
+ return (
67
+ <Section
68
+ title={t('yesterday.title')}
69
+ hint={t('yesterday.on', {
70
+ day: results.Day,
71
+ weather: t(`weather.${results.Weather}Short`),
72
+ })}
73
+ >
74
+ <Stack direction="row" spacing={3} useFlexGap sx={{ flexWrap: 'wrap', mb: 3 }}>
75
+ <Stat
76
+ id="takings"
77
+ label={t('yesterday.takings')}
78
+ value={money(symbol, results.Takings)}
79
+ />
80
+ <Stat
81
+ id="spent"
82
+ label={t('yesterday.spent')}
83
+ value={money(symbol, results.Spent)}
84
+ />
85
+ <Stat
86
+ id="profit"
87
+ label={t('yesterday.profit')}
88
+ value={money(symbol, results.Profit)}
89
+ tone={results.Profit >= 0 ? 'good' : 'bad'}
90
+ />
91
+ <Stat
92
+ id="purse"
93
+ label={t('yesterday.purse')}
94
+ value={money(symbol, results.Purse)}
95
+ />
96
+ <Stat
97
+ id="pouredaway"
98
+ label={t('yesterday.pouredAway')}
99
+ value={count(pouredAway)}
100
+ tone={pouredAway > 0 ? 'bad' : undefined}
101
+ />
102
+ <Stat
103
+ id="turnedaway"
104
+ label={t('yesterday.turnedAway')}
105
+ value={count(turnedAway)}
106
+ tone={turnedAway > 0 ? 'bad' : undefined}
107
+ />
108
+ </Stack>
109
+
110
+ <Box sx={{ overflowX: 'auto' }}>
111
+ <Table size="small">
112
+ <TableHead>
113
+ <TableRow>
114
+ <TableCell>{t('yesterday.item')}</TableCell>
115
+ <TableCell align="right">{t('yesterday.made')}</TableCell>
116
+ <TableCell align="right">{t('yesterday.wanted')}</TableCell>
117
+ <TableCell align="right">{t('yesterday.sold')}</TableCell>
118
+ <TableCell align="right">{t('yesterday.pouredAway')}</TableCell>
119
+ <TableCell align="right">{t('yesterday.turnedAway')}</TableCell>
120
+ </TableRow>
121
+ </TableHead>
122
+ <TableBody>
123
+ {menuOf(settings.menu).map(({ id, item }) => {
124
+ const line = results.Lines[id];
125
+ if (!line) return null;
126
+ return (
127
+ <TableRow key={id} data-testid={`line-${id}`}>
128
+ <TableCell>{item.Name}</TableCell>
129
+ <TableCell align="right">{count(line.Made)}</TableCell>
130
+ <TableCell align="right">{count(line.Wanted)}</TableCell>
131
+ <TableCell align="right">{count(line.Sold)}</TableCell>
132
+ <TableCell align="right">{count(line.PouredAway)}</TableCell>
133
+ <TableCell align="right">{count(line.TurnedAway)}</TableCell>
134
+ </TableRow>
135
+ );
136
+ })}
137
+ </TableBody>
138
+ </Table>
139
+ </Box>
140
+ </Section>
141
+ );
142
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Display helpers shared by the panels.
3
+ *
4
+ * Money is held in pence everywhere, so the sums stay in whole numbers and
5
+ * nothing accumulates a rounding error over ten days. It becomes pounds here,
6
+ * at the edge, and nowhere else.
7
+ */
8
+
9
+ export const money = (symbol: string, pence: number) =>
10
+ `${pence < 0 ? '-' : ''}${symbol}${(Math.abs(pence) / 100).toFixed(2)}`;
11
+
12
+ export const count = (value: number) => Math.round(value).toLocaleString();
@@ -0,0 +1,95 @@
1
+ import i18n from 'i18next';
2
+ import { initReactI18next } from 'react-i18next';
3
+
4
+ /**
5
+ * Every string on the surface lives here, including the ones nobody expects to
6
+ * translate. A game that reaches a second locale by hunting for hard-coded
7
+ * strings finds about two thirds of them.
8
+ *
9
+ * The host tells the surface which locale the session is in, and `main.tsx`
10
+ * hands that to i18next. Adding a locale is a key in `resources` plus an entry
11
+ * in `supportedLocales` in the manifest.
12
+ */
13
+ const resources = {
14
+ en: {
15
+ translation: {
16
+ shell: {
17
+ untitledTeam: 'Your stall',
18
+ looksAfter: 'You look after the {{item}}',
19
+ day: 'Day {{day}}',
20
+ processing: 'The day is being played out',
21
+ briefing: 'Notice',
22
+ writeFailed: 'That did not reach the stall. Try again.',
23
+ weekOver: 'The week is over. Nothing left to open.',
24
+ countedNextDay: 'The day turned as you changed that, so it counts for day {{day}}.',
25
+ },
26
+ weather: {
27
+ sunny: 'Sunny today. It is going to be busy.',
28
+ cloudy: 'Cloudy today. An ordinary sort of day.',
29
+ rainy: 'Rain today. Not many people about.',
30
+ sunnyShort: 'Sunny',
31
+ cloudyShort: 'Cloudy',
32
+ rainyShort: 'Rainy',
33
+ },
34
+ day: {
35
+ making: 'Making {{planned}} of a possible {{capacity}}',
36
+ ready: 'Open up, and day {{day}} plays out',
37
+ tooMany: 'Take something off before you open up',
38
+ nothing: 'Decide what to make first',
39
+ open: 'Open the stall',
40
+ },
41
+ today: {
42
+ title: 'What shall we make today?',
43
+ capacity: '{{planned}} of {{capacity}}',
44
+ room: 'Room for {{room}} more. The stall can make {{capacity}} things in a day.',
45
+ over: 'That is {{over}} more than the stall can make. Somebody needs to take some off.',
46
+ observing: 'You are watching this stall rather than working on it.',
47
+ },
48
+ make: {
49
+ yours: 'Yours',
50
+ line: '{{price}} each, costs {{cost}} to make. About {{expected}} people will want one today.',
51
+ more: 'Make more {{name}}',
52
+ fewer: 'Make less {{name}}',
53
+ },
54
+ yesterday: {
55
+ title: 'How yesterday went',
56
+ on: 'Day {{day}}, {{weather}}',
57
+ none: 'Nothing sold yet',
58
+ firstDay: 'Set your numbers, and the stall opens when the facilitator says so.',
59
+ item: 'Item',
60
+ made: 'Made',
61
+ wanted: 'Wanted',
62
+ sold: 'Sold',
63
+ pouredAway: 'Poured away',
64
+ turnedAway: 'Turned away',
65
+ takings: 'Took',
66
+ spent: 'Spent',
67
+ profit: 'Made',
68
+ purse: 'In the jar',
69
+ },
70
+ history: {
71
+ title: 'Every day so far',
72
+ hint: 'What you made, what people wanted, and what was left.',
73
+ day: 'Day',
74
+ weather: 'Weather',
75
+ },
76
+ theme: {
77
+ label: 'Light or dark',
78
+ light: 'Light',
79
+ dark: 'Dark',
80
+ system: 'Match my device',
81
+ },
82
+ },
83
+ },
84
+ };
85
+
86
+ i18n.use(initReactI18next).init({
87
+ resources,
88
+ lng: 'en',
89
+ fallbackLng: 'en',
90
+ interpolation: {
91
+ escapeValue: false,
92
+ },
93
+ });
94
+
95
+ 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,18 @@
1
+ import type { Context, MenuItem, Settings } from '../types';
2
+
3
+ export type MenuRow = { id: string; item: MenuItem };
4
+
5
+ /**
6
+ * The menu, in the order the game gave it.
7
+ *
8
+ * Every screen lists the items the same way round. Sorting on a declared
9
+ * `Order` rather than trusting object key order means a menu that arrives from
10
+ * a scenario, or through JSON, still reads the same.
11
+ */
12
+ export const menuOf = (menu: Settings['menu']): MenuRow[] =>
13
+ Object.entries(menu.Menu)
14
+ .map(([id, item]) => ({ id, item }))
15
+ .sort((a, b) => a.item.Order - b.item.Order);
16
+
17
+ export const totalPlanned = (make: Context['plan']['Make']) =>
18
+ Object.values(make).reduce((sum, value) => sum + value, 0);