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