@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,391 @@
1
+ # Common patterns
2
+
3
+ Three patterns for an engine's state and logic, each built up from the CLI commands that create its resources: a stock level that never goes negative, revenue computed in a second step, and a production loop split into one step per concern. The second builds on the first; the third stands alone.
4
+
5
+ For the shape of a whole game, start from the starter game `new package` gives you: [A tour of the starter game](./starter-game.md) walks through it, and [Design state for concurrent writes](../recipes/design-state-for-concurrent-writes.md) states the rules it follows on their own.
6
+
7
+ ## Table of contents
8
+
9
+ - [Basic inventory system](#basic-inventory-system)
10
+ - [Pricing and revenue](#pricing-and-revenue)
11
+ - [Complex state management](#complex-state-management)
12
+
13
+ ## Basic inventory system
14
+
15
+ A simple system that tracks stock and prevents negative inventory.
16
+
17
+ **Capability:**
18
+
19
+ ```bash
20
+ pnpm run sdk new capability '{
21
+ "definition": {
22
+ "identifier": "inventory",
23
+ "variablesSchema": {
24
+ "type": "object",
25
+ "properties": {
26
+ "Stock": { "type": "number", "default": 100, "minimum": 0 },
27
+ "ReorderPoint": { "type": "number", "default": 20 }
28
+ },
29
+ "required": ["Stock", "ReorderPoint"],
30
+ "additionalProperties": false
31
+ }
32
+ }
33
+ }'
34
+ ```
35
+
36
+ **Engine:**
37
+
38
+ A game holds one engine, in `engine/`, so it carries no identifier of its own. Its parameters go in a settings resource it lists in `settings`:
39
+
40
+ ```bash
41
+ pnpm run sdk new settings '{
42
+ "definition": {
43
+ "identifier": "engine",
44
+ "settingsSchema": {
45
+ "type": "object",
46
+ "properties": {
47
+ "DailySales": { "type": "number", "default": 5 }
48
+ },
49
+ "additionalProperties": false
50
+ }
51
+ }
52
+ }'
53
+
54
+ pnpm run sdk new engine '{
55
+ "definition": {
56
+ "writes": ["inventory"],
57
+ "settings": ["engine"]
58
+ },
59
+ "logic": {
60
+ "directory": ".",
61
+ "entrypoint": "logic.ts"
62
+ },
63
+ "rootDirectory": "."
64
+ }'
65
+ ```
66
+
67
+ **Logic** (`engine/logic.ts`)**:**
68
+
69
+ ```typescript
70
+ import { RunFunction } from '@seriousgaming/sdk';
71
+ import type { Context, Settings } from './types';
72
+
73
+ export const Run: RunFunction<Context, Settings> = async ({ context, settings }) => {
74
+ let { Stock } = context.inventory;
75
+
76
+ // Reduce stock
77
+ Stock -= settings.engine.DailySales;
78
+
79
+ // Prevent negative stock
80
+ if (Stock < 0) {
81
+ Stock = 0;
82
+ }
83
+
84
+ return {
85
+ capabilities: {
86
+ ...context,
87
+ inventory: {
88
+ ...context.inventory,
89
+ Stock,
90
+ },
91
+ },
92
+ };
93
+ };
94
+ ```
95
+
96
+ ## Pricing and revenue
97
+
98
+ Calculate revenue based on inventory and pricing.
99
+
100
+ **Capabilities:**
101
+
102
+ ```bash
103
+ pnpm run sdk new capability '{
104
+ "definition": {
105
+ "identifier": "pricing",
106
+ "variablesSchema": {
107
+ "type": "object",
108
+ "properties": {
109
+ "Price": { "type": "number", "default": 0 },
110
+ "PriceHistory": { "type": "array", "default": [] }
111
+ },
112
+ "required": ["Price", "PriceHistory"],
113
+ "additionalProperties": false
114
+ }
115
+ }
116
+ }'
117
+
118
+ pnpm run sdk new capability '{
119
+ "definition": {
120
+ "identifier": "revenue",
121
+ "variablesSchema": {
122
+ "type": "object",
123
+ "properties": {
124
+ "TotalRevenue": { "type": "number", "default": 0 },
125
+ "ThisTickRevenue": { "type": "number", "default": 0 }
126
+ },
127
+ "required": ["TotalRevenue", "ThisTickRevenue"],
128
+ "additionalProperties": false
129
+ }
130
+ }
131
+ }'
132
+ ```
133
+
134
+ **Engine:**
135
+
136
+ There is no second engine to create: revenue is another step of the one engine the game already holds. Add the conversion rate to its settings, and update its declarations so it reads the new capabilities and writes `revenue`:
137
+
138
+ ```bash
139
+ pnpm run sdk update settings engine '{
140
+ "definition": {
141
+ "identifier": "engine",
142
+ "settingsSchema": {
143
+ "type": "object",
144
+ "properties": {
145
+ "DailySales": { "type": "number", "default": 5 },
146
+ "ConversionRate": { "type": "number", "default": 0.8 }
147
+ },
148
+ "additionalProperties": false
149
+ }
150
+ }
151
+ }'
152
+
153
+ pnpm run sdk update engine '{
154
+ "definition": {
155
+ "reads": ["pricing"],
156
+ "writes": ["inventory", "revenue"],
157
+ "settings": ["engine"]
158
+ },
159
+ "logic": {
160
+ "directory": ".",
161
+ "entrypoint": "logic.ts"
162
+ },
163
+ "rootDirectory": "."
164
+ }'
165
+ ```
166
+
167
+ **Logic:** move the stock calculation into a step file of its own, add the revenue step beside it, and let `logic.ts` call them in order.
168
+
169
+ `engine/inventory.ts`:
170
+
171
+ ```typescript
172
+ import type { StepFunction } from '@seriousgaming/sdk';
173
+ import type { Context, Settings } from './types';
174
+
175
+ export const runInventory: StepFunction<Context, Settings> = async ({ context, settings }) => {
176
+ const Stock = Math.max(0, context.inventory.Stock - settings.engine.DailySales);
177
+
178
+ return {
179
+ ...context,
180
+ inventory: {
181
+ ...context.inventory,
182
+ Stock,
183
+ },
184
+ };
185
+ };
186
+ ```
187
+
188
+ `engine/sales.ts`:
189
+
190
+ ```typescript
191
+ import type { StepFunction } from '@seriousgaming/sdk';
192
+ import type { Context, Settings } from './types';
193
+
194
+ export const runSales: StepFunction<Context, Settings> = async ({ context, settings }) => {
195
+ const { Stock } = context.inventory;
196
+ const { Price } = context.pricing;
197
+ const { TotalRevenue } = context.revenue;
198
+
199
+ // Calculate potential sales
200
+ const potentialSales = Stock * Price;
201
+
202
+ // Apply conversion rate
203
+ const actualRevenue = potentialSales * settings.engine.ConversionRate;
204
+
205
+ return {
206
+ ...context,
207
+ revenue: {
208
+ ...context.revenue,
209
+ TotalRevenue: TotalRevenue + actualRevenue,
210
+ ThisTickRevenue: actualRevenue,
211
+ },
212
+ };
213
+ };
214
+ ```
215
+
216
+ `engine/logic.ts`:
217
+
218
+ ```typescript
219
+ import { RunFunction } from '@seriousgaming/sdk';
220
+ import { runInventory } from './inventory';
221
+ import { runSales } from './sales';
222
+ import type { Context, Settings } from './types';
223
+
224
+ export const Run: RunFunction<Context, Settings> = async (args) => {
225
+ let context = args.context;
226
+ context = await runInventory({ ...args, context });
227
+ context = await runSales({ ...args, context });
228
+ return { capabilities: context };
229
+ };
230
+ ```
231
+
232
+ Each step is a `StepFunction`: it takes the context the previous one returned and returns the next one, flat, so the order of the calls is the order the simulation runs in, visible in one place. Only the exported `Run` wraps the final context in `{ capabilities }`.
233
+
234
+ ## Complex state management
235
+
236
+ Managing state across several steps of one engine.
237
+
238
+ **Scenario:** a production system that tracks demand, production, and costs.
239
+
240
+ **Capabilities:**
241
+
242
+ ```bash
243
+ # Demand capability
244
+ pnpm run sdk new capability '{
245
+ "definition": {
246
+ "identifier": "demand",
247
+ "variablesSchema": {
248
+ "type": "object",
249
+ "properties": {
250
+ "CurrentDemand": { "type": "number", "default": 0 },
251
+ "DemandHistory": { "type": "array", "default": [] }
252
+ },
253
+ "required": ["CurrentDemand", "DemandHistory"],
254
+ "additionalProperties": false
255
+ }
256
+ }
257
+ }'
258
+
259
+ # Production capability
260
+ pnpm run sdk new capability '{
261
+ "definition": {
262
+ "identifier": "production",
263
+ "variablesSchema": {
264
+ "type": "object",
265
+ "properties": {
266
+ "ProductionRate": { "type": "number", "default": 10 },
267
+ "TotalProduced": { "type": "number", "default": 0 }
268
+ },
269
+ "required": ["ProductionRate", "TotalProduced"],
270
+ "additionalProperties": false
271
+ }
272
+ }
273
+ }'
274
+
275
+ # Costs capability
276
+ pnpm run sdk new capability '{
277
+ "definition": {
278
+ "identifier": "costs",
279
+ "variablesSchema": {
280
+ "type": "object",
281
+ "properties": {
282
+ "ProductionCost": { "type": "number", "default": 5 },
283
+ "TotalCosts": { "type": "number", "default": 0 }
284
+ },
285
+ "required": ["ProductionCost", "TotalCosts"],
286
+ "additionalProperties": false
287
+ }
288
+ }
289
+ }'
290
+ ```
291
+
292
+ The steps below read `BaseDemand`, `DemandVariance`, and `ProductionCost` from `settings/engine/`.
293
+
294
+ **Demand step** (`engine/demand.ts`)**:**
295
+
296
+ ```typescript
297
+ export const runDemand: StepFunction<Context, Settings> = async ({ context, settings }) => {
298
+ let demand = settings.engine.BaseDemand;
299
+
300
+ // Demand varies by sine wave for realistic pattern
301
+ const tick = context.demand.DemandHistory.length;
302
+ demand += Math.sin(tick * 0.5) * settings.engine.DemandVariance;
303
+ demand = Math.max(0, demand);
304
+
305
+ return {
306
+ ...context,
307
+ demand: {
308
+ ...context.demand,
309
+ CurrentDemand: demand,
310
+ DemandHistory: [...context.demand.DemandHistory, demand],
311
+ },
312
+ };
313
+ };
314
+ ```
315
+
316
+ **Production step** (`engine/production.ts`)**:**
317
+
318
+ ```typescript
319
+ export const runProduction: StepFunction<Context, Settings> = async ({ context, settings }) => {
320
+ const { CurrentDemand } = context.demand;
321
+ const { ProductionRate, TotalProduced } = context.production;
322
+
323
+ // Adjust production based on demand
324
+ let adjustedProduction = ProductionRate;
325
+ if (CurrentDemand > ProductionRate) {
326
+ adjustedProduction = ProductionRate * 1.1; // Increase by 10%
327
+ }
328
+
329
+ return {
330
+ ...context,
331
+ production: {
332
+ ...context.production,
333
+ ProductionRate: adjustedProduction,
334
+ TotalProduced: TotalProduced + adjustedProduction,
335
+ },
336
+ };
337
+ };
338
+ ```
339
+
340
+ **Costs step** (`engine/costs.ts`)**:**
341
+
342
+ ```typescript
343
+ export const runCosts: StepFunction<Context, Settings> = async ({ context, settings }) => {
344
+ const { ProductionRate } = context.production;
345
+ const { TotalCosts } = context.costs;
346
+
347
+ const costThisTick = ProductionRate * settings.engine.ProductionCost;
348
+
349
+ return {
350
+ ...context,
351
+ costs: {
352
+ ...context.costs,
353
+ ProductionCost: settings.engine.ProductionCost,
354
+ TotalCosts: TotalCosts + costThisTick,
355
+ },
356
+ };
357
+ };
358
+ ```
359
+
360
+ **Ordering** (`engine/logic.ts`)**:**
361
+
362
+ ```typescript
363
+ import { RunFunction } from '@seriousgaming/sdk';
364
+ import { runCosts } from './costs';
365
+ import { runDemand } from './demand';
366
+ import { runProduction } from './production';
367
+ import type { Context, Settings } from './types';
368
+
369
+ export const Run: RunFunction<Context, Settings> = async (args) => {
370
+ let context = args.context;
371
+ context = await runDemand({ ...args, context });
372
+ context = await runProduction({ ...args, context });
373
+ context = await runCosts({ ...args, context });
374
+ return { capabilities: context };
375
+ };
376
+ ```
377
+
378
+ ## Best practices summary
379
+
380
+ 1. **Separate concerns**: one step per responsibility
381
+ 2. **Clear dependencies**: declare what you need explicitly
382
+ 3. **Immutable updates**: always use spread operators
383
+ 4. **Type safety**: use generated types
384
+ 5. **Test logic**: write unit tests
385
+ 6. **Document code**: comments for non-obvious logic
386
+
387
+ ## Next steps
388
+
389
+ - [Creating engines](./engine.md): the reference for `Init`, `Run`, steps and the engine's private state
390
+ - [Creating capabilities](./capabilities.md): scopes, lifetimes and typing a capability's variables
391
+ - [Engine testing](./engine-testing.md): play a few ticks of a pattern in-process before building a UI on it
@@ -0,0 +1,196 @@
1
+ # Building presentations
2
+
3
+ Ship the facilitator's slide deck as part of the game: an introduction, the briefing, a divider before each block of rounds, the debriefs and the results, localised, aware of the scenario, live with the session's results, and exportable for anyone who wants to take it away and edit it.
4
+
5
+ **Keywords:** presentation, slides, slide deck, deck, briefing, debrief, results, PowerPoint, PDF, PNG, export, usePresentation, PresentationWrapper, @seriousgaming/deck, Deck, Slide, Step
6
+
7
+ ## What you'll learn
8
+
9
+ - What a presentation is, and what a runtime does with it
10
+ - How a deck reads the game and the session, and why `session` can be `null`
11
+ - How to build slides with `Deck`, `Slide`, `Step` and the building blocks
12
+ - How a deck remembers where the facilitator was
13
+ - What export produces, and the habits that keep an export faithful
14
+
15
+ ## What is a presentation?
16
+
17
+ A presentation is the slides a facilitator shows the room. A game holds at most one, in the `presentation/` slot. It is an ordinary UI bundle of the game's own, so the runtime renders it without knowing what a slide is: navigation, animation, layout and export all happen inside the frame.
18
+
19
+ Every runtime offers it, so a game can rely on its deck being there for the briefing and the debrief. Three things set it apart from the other surfaces:
20
+
21
+ - **Read-only.** It declares `reads` and `settings`, and no `writes`, `uses` or `endpoints`. The host refuses anything it sends except a read of past rounds
22
+ - **Facilitator only.** It is never shown on a participant's or an observer's device. The room sees it because the facilitator shares or projects their screen
23
+ - **Shown with or without a session.** It is the one surface a runtime shows with no session at all: from a game's page in a library, while a facilitator rehearses, or to export it before the day
24
+
25
+ ## Creating one
26
+
27
+ ```bash
28
+ pnpm run sdk new presentation '{
29
+ "definition": {
30
+ "reads": ["results"],
31
+ "settings": ["engine"]
32
+ },
33
+ "rootDirectory": ".",
34
+ "ui": {
35
+ "entrypoint": "index.html"
36
+ }
37
+ }'
38
+ ```
39
+
40
+ The slot is the presentation's identifier, so `update presentation` and `delete presentation` take none. The scaffold depends on `@seriousgaming/deck`, the SDK's deck library, which a newly scaffolded package already lists. See [Create a presentation](../recipes/create-presentation.md) for the step-by-step version.
41
+
42
+ The deck library is optional. A presentation is a UI bundle like any other, rendered under `PresentationWrapper`, so a deck written with another library, or none, works the same way. The rest of this guide assumes the SDK's own.
43
+
44
+ ## Reading the game and the session
45
+
46
+ `usePresentation()` gives a deck everything it can see:
47
+
48
+ ```tsx
49
+ import { usePresentation } from '@seriousgaming/sdk/ui';
50
+
51
+ import type { Settings, SharedContext, TeamContext } from '../types';
52
+
53
+ const { game, session, settings, theme, locale, getPastState, getAssetUrl } = usePresentation<
54
+ Settings,
55
+ TeamContext,
56
+ SharedContext
57
+ >();
58
+ ```
59
+
60
+ | Field | What it holds |
61
+ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
62
+ | `game` | `name` in the current locale, `maxTicks` (or `null` when the engine or tick controller decides), `mode`, and `scenarioName` (or `null`). Present with or without a session |
63
+ | `session` | `null` with no session. Otherwise its `id`, `name`, `tick` (the highest any team has reached), `data` (the shared store and every team, narrowed to `reads`) and `teams` |
64
+ | `settings` | The settings resources the presentation declares, resolved for the scenario, and for the session when there is one |
65
+ | `theme` | The host's theme, to map onto the deck's colours |
66
+ | `locale` | The locale to render in |
67
+ | `getPastState` | Reads past rounds, as the session UI does, with `teams` defaulting to every team. Rejects when there is no session |
68
+ | `getAssetUrl` | Turns an `asset:`, `shared:` or `blob:` reference into a URL |
69
+
70
+ A presentation sees every team: it is the facilitator's view of the room.
71
+
72
+ The shared hooks work in a deck too: `useAssetUrl` and `useAsset` for assets, and `usePastState` in place of calling `getPastState` from an effect (it rejects into `error` when there is no session). `PresentationWrapper` sets the document's `lang`, `dir` and `color-scheme` from the locale and theme, as every wrapper does.
73
+
74
+ ### When `session` is `null`
75
+
76
+ A deck must render with `session: null`. Wherever a slide would show results, show sample or placeholder figures, and label them as such on the slide so nobody mistakes them for the room's own. One hook that returns either the session's figures or a sample set flagged `live: false` keeps the branch in one place, and each results slide adds a note when `live` is false:
77
+
78
+ ```typescript
79
+ export function useResults(): Results {
80
+ const { session } = usePresentation<Settings, TeamContext>();
81
+ if (!session || session.completedTicks < 1) return SAMPLE; // { live: false, ... }
82
+ return {
83
+ live: true,
84
+ teams: session.teams.map((team) => ({
85
+ name: team.name,
86
+ profit: session.data.teams[team.teamId]?.context.profit?.Profit ?? 0,
87
+ })),
88
+ };
89
+ }
90
+ ```
91
+
92
+ ### Varying by scenario
93
+
94
+ Branch on settings, scenario assets and `game.maxTicks`, never on `game.scenarioName`. A facilitator who saves their own scenario from one of yours gets a new identity, so a deck that branched on the name would show the wrong slides to exactly the facilitators who customised. `scenarioName` is for display.
95
+
96
+ ## Slides
97
+
98
+ ```tsx
99
+ import { Bullets, Deck, Heading, Slide, Step, Text } from '@seriousgaming/deck';
100
+
101
+ <Deck
102
+ title={game.name}
103
+ lang={locale.locale}
104
+ rememberAs={session ? `session:${session.id}` : 'rehearsal'}
105
+ >
106
+ <Slide key="title">
107
+ <Heading level={1}>{game.name}</Heading>
108
+ </Slide>
109
+ <Slide key="agenda" transition="slide" notes="Reveal each point in turn.">
110
+ <Heading>{t('agendaHeading')}</Heading>
111
+ <Bullets stepped items={[t('intro'), t('play'), t('review')]} />
112
+ <Step at={4} animation="zoom">
113
+ <Text>{t('letsBegin')}</Text>
114
+ </Step>
115
+ </Slide>
116
+ </Deck>;
117
+ ```
118
+
119
+ - **`Deck`** shows one slide at a time, scaled to fit. Every direct child is one slide (arrays and fragments are flattened), so a component of your own counts as a slide only if it renders a single `Slide`
120
+ - **`Slide`** lays its content out on a fixed 1920 by 1080 canvas. Position things however you like inside it. It takes `transition` (`none`, `fade`, `slide`, `zoom` or `rise`; the deck's own `transition` by default), `notes`, `background` (any CSS background) and `id`
121
+ - **`Step`** is content revealed by a click: `at={1}` for the first, `at={2}` for the second, and steps may share a number. A hidden step keeps its space, so a slide never reflows as it builds. Its `animation` is `fade`, `fade-up` (the default), `fade-left`, `zoom`, `blur` or `none`
122
+ - **`useStepVisible(at)`** builds a custom animation with clicks without wrapping it in a `Step`
123
+
124
+ The facilitator moves with the keyboard, a clicker, a click or a swipe. Right, down, space, page down and enter advance; left, up, page up and backspace go back; home and end jump; F toggles fullscreen and O the overview. In a right-to-left deck the arrows swap. Going back lands on the previous slide fully built.
125
+
126
+ `Deck` also takes `dir`, `lang`, `brand` (the deck's colours and fonts), `controls` and `clickToAdvance` (both on by default), `exportFormats`, and `onSlideChange`.
127
+
128
+ ### Building blocks
129
+
130
+ | Block | Use it for | In PowerPoint |
131
+ | --------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
132
+ | `Heading` | A slide title, `level` 1 to 3 | Editable text |
133
+ | `Text` | Body copy, with `variant` `body`, `lead` or `caption` | Editable text |
134
+ | `Bullets` | A list, `ordered` or not, revealed one item per click with `stepped` | An editable list |
135
+ | `Table` | `columns` and `rows`, with an `align` per column | An editable table |
136
+ | `Image` | A picture. `fit="contain"` (the default) or `fit="cover"` to fill and crop | A movable picture with `contain`; part of the slide's picture with `cover` |
137
+ | `Chart` | A `bar` or `line` chart from `categories` and `series`, drawn in the deck's colours | Part of the slide's picture |
138
+
139
+ Everything else on a slide, your own components included, exports as part of the slide's picture. To make your own element export as editable text, spread `nativeText` onto it: `<div {...nativeText}>...</div>`.
140
+
141
+ ### Colours
142
+
143
+ The deck draws in its own `brand`, which a game maps from the host's theme in a line or two, so the slides pick up the same primary and secondary as every other surface:
144
+
145
+ ```tsx
146
+ const brand = {
147
+ colors: {
148
+ ...defaultBrand.colors,
149
+ primary: theme.light.primary,
150
+ secondary: theme.light.secondary,
151
+ },
152
+ };
153
+ ```
154
+
155
+ Use the light palette whatever mode the facilitator's screen is in: slides are made to be projected, and a projector washes out a dark slide. `useBrand()` gives a custom component the resolved colours, and the CSS variables `--deck-primary`, `--deck-secondary` and `--deck-muted` reach anything inside a slide.
156
+
157
+ ## Coming back to where you were
158
+
159
+ `rememberAs` keeps the facilitator's place in the browser's own storage, so a reload, a closed laptop lid or a break comes back to the same slide and step:
160
+
161
+ ```tsx
162
+ <Deck rememberAs={session ? `session:${session.id}` : 'rehearsal'} startAt="title">
163
+ ```
164
+
165
+ - Key it by session, so each session keeps its own place. `session.id` is stable for the session's life and unique to it
166
+ - The deck remembers a slide by its name (its `id`, or its React `key`) and falls back to its number, so a slide that a setting adds or removes does not shift the facilitator onto the wrong one. Give every slide a key
167
+ - `startAt` is where to open when nothing is remembered: a slide's name or number. Compute it from where the room has got to, for instance `startAt={session && session.completedTicks > 0 ? 'results' : 'title'}`
168
+
169
+ The position lives in one browser on one device. A facilitator who moves to another laptop starts at `startAt`.
170
+
171
+ ## Export
172
+
173
+ The controls offer the whole deck as a PDF (through the browser's print dialogue), as a PowerPoint file, and the current slide as a PNG. The browser's own print shortcut gives the same PDF. Every export renders each slide still and fully built.
174
+
175
+ In PowerPoint, **words are editable and visuals are pictures**. Headings, text, bullets and tables become native objects a facilitator can edit, and `notes` land in the notes pane. Charts, custom components and anything else become part of the slide's picture, so the export always matches the screen. An export is the facilitator's own file once it is downloaded: nothing about the game changes, and the game itself is never edited in place.
176
+
177
+ ## Habits that keep an export faithful
178
+
179
+ - **Use the building blocks for anything a facilitator might edit.** Text drawn by your own component exports as a picture
180
+ - **Render with `session === null`.** Every slide makes sense with no session, and labels sample figures as samples
181
+ - **Branch on settings, not the scenario name.** A facilitator's own scenario has a different name and the same settings
182
+ - **Show finished visuals in static mode.** Print, export and the overview render each slide still. A custom animation reads `useSlide().mode` and draws its end state when it is `'static'`, as in `const shown = useStepVisible(1); const on = mode === 'static' || shown;`
183
+ - **Route every word through i18n.** A deck is shown in the session's language. Set `lang` and `dir` on the `Deck` from `locale`
184
+ - **One element per slide.** Every element in the array is a slide; a wrapper around several is one slide
185
+
186
+ ## Testing
187
+
188
+ To see your deck, run `pnpm run sdk dev` and open `/presentation`, or `/presentation?preview` for the deck with no session.
189
+
190
+ ## Related
191
+
192
+ - [Create a presentation](../recipes/create-presentation.md): the step-by-step version
193
+ - [Building session UIs](./session.md): the facilitator's other surface
194
+ - [Facilitator guides](./guide.md): the run sheet beside the deck
195
+ - [Internationalisation (i18n)](./internationalization.md): translating the deck's words
196
+ - [Scenarios](./scenarios.md): what a scenario can change, and so what a deck can vary on