@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.
- package/LICENSE +93 -0
- package/README.md +36 -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 +27368 -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 +16884 -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 +209 -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 +236 -0
- package/dist/docs/cli-and-dev-server.md +217 -0
- package/dist/docs/cli-error-codes.md +154 -0
- package/dist/docs/cli-reference.md +2488 -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 +575 -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 +335 -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 +122 -3
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Alert,
|
|
3
|
+
AppBar,
|
|
4
|
+
Box,
|
|
5
|
+
Chip,
|
|
6
|
+
CssBaseline,
|
|
7
|
+
LinearProgress,
|
|
8
|
+
Snackbar,
|
|
9
|
+
Stack,
|
|
10
|
+
ThemeProvider,
|
|
11
|
+
Toolbar,
|
|
12
|
+
Typography,
|
|
13
|
+
useMediaQuery,
|
|
14
|
+
useTheme,
|
|
15
|
+
} from '@mui/material';
|
|
16
|
+
import {
|
|
17
|
+
useMutationReporter,
|
|
18
|
+
useUserInterfaceOwnTeam,
|
|
19
|
+
useUserInterfaceData,
|
|
20
|
+
useUserInterfaceParticipant,
|
|
21
|
+
useUserInterfaceValidationRejections,
|
|
22
|
+
} from '@seriousgaming/sdk/ui';
|
|
23
|
+
import { useTranslation } from 'react-i18next';
|
|
24
|
+
|
|
25
|
+
import type { Settings, Context } from '../types';
|
|
26
|
+
import DayBar from './DayBar';
|
|
27
|
+
import HistoryPanel from './HistoryPanel';
|
|
28
|
+
import { useAppTheme } from './theme';
|
|
29
|
+
import ThemeToggle from './ThemeToggle';
|
|
30
|
+
import TodayPanel from './TodayPanel';
|
|
31
|
+
import { WriteContext } from './writes';
|
|
32
|
+
import YesterdayPanel from './YesterdayPanel';
|
|
33
|
+
|
|
34
|
+
/** Wide enough for the history table without letting a card run an ultrawide monitor. */
|
|
35
|
+
const CONTENT_MAX_WIDTH = 900;
|
|
36
|
+
|
|
37
|
+
function Shell() {
|
|
38
|
+
const { data, settings, completedTicks, isRoundProcessing } = useUserInterfaceData<
|
|
39
|
+
Context,
|
|
40
|
+
Settings
|
|
41
|
+
>();
|
|
42
|
+
const participant = useUserInterfaceParticipant();
|
|
43
|
+
const ownTeam = useUserInterfaceOwnTeam();
|
|
44
|
+
const { rejections, clearRejections } = useUserInterfaceValidationRejections();
|
|
45
|
+
const reporter = useMutationReporter();
|
|
46
|
+
const { t } = useTranslation();
|
|
47
|
+
const theme = useTheme();
|
|
48
|
+
const compact = useMediaQuery(theme.breakpoints.down('sm'));
|
|
49
|
+
|
|
50
|
+
const looksAfter = participant ? data.roles.LooksAfter[participant.id] : undefined;
|
|
51
|
+
const yourItem = looksAfter ? settings.menu.Menu[looksAfter]?.Name : undefined;
|
|
52
|
+
const briefing = data.briefing.Message;
|
|
53
|
+
|
|
54
|
+
// A validator refusal arrives twice: here as a `validation-rejected`
|
|
55
|
+
// outcome, and through the rejection channel below, which carries the
|
|
56
|
+
// validator's own message and is the better thing to show. Rendering both
|
|
57
|
+
// would put a generic notice on top of a specific one.
|
|
58
|
+
const unexplained =
|
|
59
|
+
reporter.failure && reporter.failure.outcome.kind !== 'validation-rejected'
|
|
60
|
+
? reporter.failure
|
|
61
|
+
: null;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<WriteContext.Provider value={reporter}>
|
|
65
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
|
|
66
|
+
<AppBar position="static" color="default" elevation={0}>
|
|
67
|
+
<Toolbar sx={{ gap: 1 }}>
|
|
68
|
+
<Typography variant="h6" component="h1" sx={{ flexGrow: 1 }} noWrap>
|
|
69
|
+
{ownTeam?.name ?? t('shell.untitledTeam')}
|
|
70
|
+
</Typography>
|
|
71
|
+
{yourItem && !compact && (
|
|
72
|
+
<Chip size="small" label={t('shell.looksAfter', { item: yourItem })} />
|
|
73
|
+
)}
|
|
74
|
+
<Chip
|
|
75
|
+
size="small"
|
|
76
|
+
color="primary"
|
|
77
|
+
label={t('shell.day', { day: completedTicks + 1 })}
|
|
78
|
+
data-testid="day"
|
|
79
|
+
/>
|
|
80
|
+
<ThemeToggle />
|
|
81
|
+
</Toolbar>
|
|
82
|
+
</AppBar>
|
|
83
|
+
|
|
84
|
+
{/* One band per reason the screen is not simply waiting for the
|
|
85
|
+
team. Each says which reason it is: a surface that only greys
|
|
86
|
+
its controls out reads as having broken. */}
|
|
87
|
+
{isRoundProcessing && (
|
|
88
|
+
<Box sx={{ flex: '0 0 auto' }}>
|
|
89
|
+
<Alert square severity="info" sx={{ borderRadius: 0, py: 0.25 }}>
|
|
90
|
+
{t('shell.processing')}
|
|
91
|
+
</Alert>
|
|
92
|
+
<LinearProgress />
|
|
93
|
+
</Box>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{briefing !== '' && (
|
|
97
|
+
<Alert
|
|
98
|
+
square
|
|
99
|
+
severity="info"
|
|
100
|
+
icon={false}
|
|
101
|
+
sx={{ flex: '0 0 auto', borderRadius: 0 }}
|
|
102
|
+
>
|
|
103
|
+
<Stack direction="row" spacing={1} sx={{ alignItems: 'baseline' }}>
|
|
104
|
+
<Typography variant="overline">{t('shell.briefing')}</Typography>
|
|
105
|
+
<Typography variant="body2">{briefing}</Typography>
|
|
106
|
+
</Stack>
|
|
107
|
+
</Alert>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
<Box sx={{ flex: 1, minHeight: 0, overflowY: 'auto', py: 3 }}>
|
|
111
|
+
<Stack spacing={3} sx={{ maxWidth: CONTENT_MAX_WIDTH, mx: 'auto', px: 2 }}>
|
|
112
|
+
<TodayPanel />
|
|
113
|
+
<YesterdayPanel />
|
|
114
|
+
<HistoryPanel />
|
|
115
|
+
</Stack>
|
|
116
|
+
</Box>
|
|
117
|
+
|
|
118
|
+
<DayBar />
|
|
119
|
+
|
|
120
|
+
{/* The validator's own message, which is the one that says what
|
|
121
|
+
the rule was and who else was changing something. */}
|
|
122
|
+
<Snackbar
|
|
123
|
+
open={rejections.length > 0}
|
|
124
|
+
onClose={clearRejections}
|
|
125
|
+
autoHideDuration={8000}
|
|
126
|
+
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
|
127
|
+
>
|
|
128
|
+
<Alert severity="warning" onClose={clearRejections}>
|
|
129
|
+
{rejections.map((rejection) => rejection.message).join(' ')}
|
|
130
|
+
</Alert>
|
|
131
|
+
</Snackbar>
|
|
132
|
+
|
|
133
|
+
{/* What nothing else reports: denied, tick-failed, conflict, error. */}
|
|
134
|
+
<Snackbar
|
|
135
|
+
open={unexplained !== null}
|
|
136
|
+
onClose={reporter.dismiss}
|
|
137
|
+
autoHideDuration={8000}
|
|
138
|
+
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
|
139
|
+
>
|
|
140
|
+
<Alert severity="error" onClose={reporter.dismiss}>
|
|
141
|
+
{unexplained?.outcome.kind === 'tick-failed'
|
|
142
|
+
? t('shell.weekOver')
|
|
143
|
+
: t('shell.writeFailed')}
|
|
144
|
+
</Alert>
|
|
145
|
+
</Snackbar>
|
|
146
|
+
|
|
147
|
+
{/* Not a failure. The change landed, but in the day after the one
|
|
148
|
+
they were looking at, and nothing else on screen says so. */}
|
|
149
|
+
<Snackbar
|
|
150
|
+
open={reporter.late !== null}
|
|
151
|
+
onClose={reporter.dismiss}
|
|
152
|
+
autoHideDuration={8000}
|
|
153
|
+
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
|
154
|
+
>
|
|
155
|
+
<Alert severity="info" onClose={reporter.dismiss}>
|
|
156
|
+
{t('shell.countedNextDay', {
|
|
157
|
+
day: (reporter.late?.committedAtTick ?? 0) + 1,
|
|
158
|
+
})}
|
|
159
|
+
</Alert>
|
|
160
|
+
</Snackbar>
|
|
161
|
+
</Box>
|
|
162
|
+
</WriteContext.Provider>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default function App() {
|
|
167
|
+
const theme = useAppTheme();
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<ThemeProvider theme={theme}>
|
|
171
|
+
<CssBaseline enableColorScheme />
|
|
172
|
+
<Shell />
|
|
173
|
+
</ThemeProvider>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createMockParticipant, renderUserInterface } from '@seriousgaming/sdk/ui/testing';
|
|
2
|
+
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import DayBar from './DayBar';
|
|
6
|
+
import './i18n';
|
|
7
|
+
import { baseSettings, baseContext } from './test-helpers';
|
|
8
|
+
|
|
9
|
+
const ada = createMockParticipant({ id: 'participant-1', name: 'Ada' });
|
|
10
|
+
|
|
11
|
+
const render = (options: Parameters<typeof renderUserInterface>[1] = {}) =>
|
|
12
|
+
renderUserInterface(<DayBar />, {
|
|
13
|
+
data: baseContext,
|
|
14
|
+
settings: baseSettings,
|
|
15
|
+
participant: ada,
|
|
16
|
+
...options,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('opening the stall', () => {
|
|
20
|
+
/**
|
|
21
|
+
* The stalls run their own days. `requestTick` from a player surface
|
|
22
|
+
* advances that team and nobody else, which is the whole point of
|
|
23
|
+
* independent teams: a stall that has decided gets on with it instead of
|
|
24
|
+
* waiting for the room.
|
|
25
|
+
*/
|
|
26
|
+
it('is the players’ to do, not the facilitator’s', async () => {
|
|
27
|
+
const requestTick = vi.fn().mockResolvedValue(undefined);
|
|
28
|
+
render({ callbacks: { requestTick } });
|
|
29
|
+
|
|
30
|
+
fireEvent.click(screen.getByTestId('open-stall'));
|
|
31
|
+
|
|
32
|
+
await waitFor(() => expect(requestTick).toHaveBeenCalled());
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('says which day is about to play out', () => {
|
|
36
|
+
render({ completedTicks: 4 });
|
|
37
|
+
|
|
38
|
+
expect(screen.getByText(/day 5 plays out/)).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('says what the stall is making, against what it could', () => {
|
|
42
|
+
render();
|
|
43
|
+
|
|
44
|
+
expect(screen.getByText('Making 70 of a possible 120')).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('will not open a stall that has decided nothing', () => {
|
|
48
|
+
render({
|
|
49
|
+
data: { ...baseContext, plan: { Make: { lemonade: 0, icedtea: 0, cookies: 0 } } },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(screen.getByTestId('open-stall')).toBeDisabled();
|
|
53
|
+
expect(screen.getByText('Decide what to make first')).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// The engine would happily make 150 things; the game says the stall cannot.
|
|
57
|
+
// Blocking here is kinder than letting a day play out on a plan the
|
|
58
|
+
// validator has already been refusing.
|
|
59
|
+
it('will not open a stall planning more than it can make', () => {
|
|
60
|
+
render({
|
|
61
|
+
data: {
|
|
62
|
+
...baseContext,
|
|
63
|
+
plan: { Make: { lemonade: 90, icedtea: 40, cookies: 20 } },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(screen.getByTestId('open-stall')).toBeDisabled();
|
|
68
|
+
expect(screen.getByText('Take something off before you open up')).toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// An observer has no participant identity, so there is nothing for the
|
|
72
|
+
// host to attribute a tick to.
|
|
73
|
+
it('is not offered to somebody observing', () => {
|
|
74
|
+
render({ viewerRole: 'observer', participant: null });
|
|
75
|
+
|
|
76
|
+
expect(screen.getByTestId('open-stall')).toBeDisabled();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Box, Button, Paper, Stack, Typography } from '@mui/material';
|
|
2
|
+
import {
|
|
3
|
+
useUserInterfaceCallbacks,
|
|
4
|
+
useUserInterfaceData,
|
|
5
|
+
useUserInterfaceParticipant,
|
|
6
|
+
} from '@seriousgaming/sdk/ui';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
|
|
9
|
+
import type { Settings, Context } from '../types';
|
|
10
|
+
import { totalPlanned } from './menu';
|
|
11
|
+
import { useWrite } from './writes';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The stall opens its own day.
|
|
15
|
+
*
|
|
16
|
+
* `requestTick` from a player surface advances that team and nobody else, which
|
|
17
|
+
* is what independent teams are for: a stall that has made its mind up gets on
|
|
18
|
+
* with it instead of waiting for the room. The facilitator watches, and can
|
|
19
|
+
* undo a day, but does not drive the session.
|
|
20
|
+
*
|
|
21
|
+
* It is a shared control with no lock on it, which is deliberate. Somebody can
|
|
22
|
+
* open the stall while a team-mate is still moving a slider, and when that
|
|
23
|
+
* happens the change lands on the following day rather than being lost: that is
|
|
24
|
+
* what `reporter.late` reports, and why the notice for it exists.
|
|
25
|
+
*/
|
|
26
|
+
export default function DayBar() {
|
|
27
|
+
const { data, settings, completedTicks } = useUserInterfaceData<Context, Settings>();
|
|
28
|
+
const participant = useUserInterfaceParticipant();
|
|
29
|
+
const { requestTick } = useUserInterfaceCallbacks();
|
|
30
|
+
const { run, pending } = useWrite();
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
|
|
33
|
+
const planned = totalPlanned(data.plan.Make);
|
|
34
|
+
const over = planned > settings.menu.Capacity;
|
|
35
|
+
const empty = planned === 0;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Paper
|
|
39
|
+
square
|
|
40
|
+
elevation={3}
|
|
41
|
+
sx={{ flex: '0 0 auto', borderTop: 1, borderColor: 'divider', px: 2, py: 1.5 }}
|
|
42
|
+
>
|
|
43
|
+
<Stack
|
|
44
|
+
direction="row"
|
|
45
|
+
spacing={2}
|
|
46
|
+
sx={{ alignItems: 'center', justifyContent: 'space-between', mx: 'auto' }}
|
|
47
|
+
>
|
|
48
|
+
<Box>
|
|
49
|
+
<Typography variant="subtitle2">
|
|
50
|
+
{t('day.making', { planned, capacity: settings.menu.Capacity })}
|
|
51
|
+
</Typography>
|
|
52
|
+
<Typography variant="caption" color="text.secondary">
|
|
53
|
+
{over
|
|
54
|
+
? t('day.tooMany')
|
|
55
|
+
: empty
|
|
56
|
+
? t('day.nothing')
|
|
57
|
+
: t('day.ready', { day: completedTicks + 1 })}
|
|
58
|
+
</Typography>
|
|
59
|
+
</Box>
|
|
60
|
+
<Button
|
|
61
|
+
variant="contained"
|
|
62
|
+
size="large"
|
|
63
|
+
disabled={!participant || pending || over || empty}
|
|
64
|
+
data-testid="open-stall"
|
|
65
|
+
onClick={() => void run(() => requestTick(), 'open-stall')}
|
|
66
|
+
>
|
|
67
|
+
{t('day.open')}
|
|
68
|
+
</Button>
|
|
69
|
+
</Stack>
|
|
70
|
+
</Paper>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { renderUserInterface } from '@seriousgaming/sdk/ui/testing';
|
|
2
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import type { Context } from '../types';
|
|
6
|
+
import HistoryPanel from './HistoryPanel';
|
|
7
|
+
import './i18n';
|
|
8
|
+
import { baseSettings, baseContext, playedDay } from './test-helpers';
|
|
9
|
+
|
|
10
|
+
/** Day `n`'s results, as the round that closed it recorded them. */
|
|
11
|
+
function dayResults(n: number, profit: number): Context['results'] {
|
|
12
|
+
return { ...playedDay.results, Day: n, Profit: profit, Purse: profit * n };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('HistoryPanel', () => {
|
|
16
|
+
it('shows nothing before the first day has been played', () => {
|
|
17
|
+
renderUserInterface(<HistoryPanel />, { data: baseContext, settings: baseSettings });
|
|
18
|
+
|
|
19
|
+
expect(screen.queryByText('Every day so far')).not.toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('lists the earlier days from the host, and today from live context', async () => {
|
|
23
|
+
const getPastState = vi.fn(async () => [
|
|
24
|
+
{
|
|
25
|
+
teamId: 'team-1',
|
|
26
|
+
index: 0,
|
|
27
|
+
name: 'Team 1',
|
|
28
|
+
tick: 1,
|
|
29
|
+
recordedAt: 0,
|
|
30
|
+
complete: false,
|
|
31
|
+
context: { results: dayResults(1, 100) },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
teamId: 'team-1',
|
|
35
|
+
index: 0,
|
|
36
|
+
name: 'Team 1',
|
|
37
|
+
tick: 2,
|
|
38
|
+
recordedAt: 0,
|
|
39
|
+
complete: false,
|
|
40
|
+
context: { results: dayResults(2, 200) },
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
renderUserInterface(<HistoryPanel />, {
|
|
45
|
+
data: { ...playedDay, results: dayResults(3, 300) },
|
|
46
|
+
settings: baseSettings,
|
|
47
|
+
completedTicks: 3,
|
|
48
|
+
callbacks: { getPastState: getPastState as never },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await waitFor(() => expect(screen.getByTestId('day-1')).toBeInTheDocument());
|
|
52
|
+
expect(screen.getByTestId('day-2')).toBeInTheDocument();
|
|
53
|
+
expect(screen.getByTestId('day-3')).toBeInTheDocument();
|
|
54
|
+
expect(getPastState).toHaveBeenCalledWith({
|
|
55
|
+
fromTick: 1,
|
|
56
|
+
toTick: 3,
|
|
57
|
+
capabilityIds: ['results'],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('still shows today when the history cannot be read', async () => {
|
|
62
|
+
renderUserInterface(<HistoryPanel />, {
|
|
63
|
+
data: { ...playedDay, results: dayResults(2, 200) },
|
|
64
|
+
settings: baseSettings,
|
|
65
|
+
completedTicks: 2,
|
|
66
|
+
callbacks: {
|
|
67
|
+
getPastState: vi.fn(async () => Promise.reject(new Error('no'))) as never,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await waitFor(() => expect(screen.getByTestId('day-2')).toBeInTheDocument());
|
|
72
|
+
expect(screen.queryByTestId('day-1')).not.toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Box, Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material';
|
|
2
|
+
import { useUserInterfaceCallbacks, useUserInterfaceData } from '@seriousgaming/sdk/ui';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import type { Settings, Context } from '../types';
|
|
7
|
+
import { count, money } from './format';
|
|
8
|
+
import Section from './Section';
|
|
9
|
+
|
|
10
|
+
type DayResults = Context['results'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Every day the stall has traded, read back from the host's history.
|
|
14
|
+
*
|
|
15
|
+
* The engine keeps only today's `results` in context. Each earlier day is in
|
|
16
|
+
* the round the team closed, which `getPastState` returns, so the table asks
|
|
17
|
+
* for those and adds today's from live context. Nothing grows in context as
|
|
18
|
+
* the days go by, which is what keeps a long session cheap.
|
|
19
|
+
*
|
|
20
|
+
* The history changes only when a tick commits or is undone, and both change
|
|
21
|
+
* `completedTicks`, so that is what the fetch waits on.
|
|
22
|
+
*/
|
|
23
|
+
export default function HistoryPanel() {
|
|
24
|
+
const { data, settings, completedTicks } = useUserInterfaceData<Context, Settings>();
|
|
25
|
+
const { getPastState } = useUserInterfaceCallbacks();
|
|
26
|
+
const { t } = useTranslation();
|
|
27
|
+
const symbol = settings.ui.CurrencySymbol;
|
|
28
|
+
|
|
29
|
+
const [past, setPast] = useState<DayResults[]>([]);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (completedTicks < 2) {
|
|
33
|
+
setPast([]);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let cancelled = false;
|
|
37
|
+
getPastState<Context>({ fromTick: 1, toTick: completedTicks, capabilityIds: ['results'] })
|
|
38
|
+
.then((entries) => {
|
|
39
|
+
if (!cancelled) setPast(entries.map((entry) => entry.context.results));
|
|
40
|
+
})
|
|
41
|
+
// History is a nice-to-have on this screen: a failed read leaves the
|
|
42
|
+
// table showing today alone rather than an error.
|
|
43
|
+
.catch(() => {
|
|
44
|
+
if (!cancelled) setPast([]);
|
|
45
|
+
});
|
|
46
|
+
return () => {
|
|
47
|
+
cancelled = true;
|
|
48
|
+
};
|
|
49
|
+
}, [completedTicks, getPastState]);
|
|
50
|
+
|
|
51
|
+
const byDay = new Map<number, DayResults>();
|
|
52
|
+
for (const results of [...past, data.results]) {
|
|
53
|
+
if (results.Day > 0) byDay.set(results.Day, results);
|
|
54
|
+
}
|
|
55
|
+
const days = [...byDay.values()].sort((a, b) => a.Day - b.Day);
|
|
56
|
+
|
|
57
|
+
if (days.length === 0) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const best = Math.max(...days.map((day) => day.Profit));
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Section title={t('history.title')} hint={t('history.hint')}>
|
|
65
|
+
<Box sx={{ overflowX: 'auto' }}>
|
|
66
|
+
<Table size="small">
|
|
67
|
+
<TableHead>
|
|
68
|
+
<TableRow>
|
|
69
|
+
<TableCell>{t('history.day')}</TableCell>
|
|
70
|
+
<TableCell>{t('history.weather')}</TableCell>
|
|
71
|
+
<TableCell align="right">{t('yesterday.made')}</TableCell>
|
|
72
|
+
<TableCell align="right">{t('yesterday.sold')}</TableCell>
|
|
73
|
+
<TableCell align="right">{t('yesterday.pouredAway')}</TableCell>
|
|
74
|
+
<TableCell align="right">{t('yesterday.turnedAway')}</TableCell>
|
|
75
|
+
<TableCell align="right">{t('yesterday.profit')}</TableCell>
|
|
76
|
+
<TableCell align="right">{t('yesterday.purse')}</TableCell>
|
|
77
|
+
</TableRow>
|
|
78
|
+
</TableHead>
|
|
79
|
+
<TableBody>
|
|
80
|
+
{days.map((day) => {
|
|
81
|
+
const totals = totalsOf(day);
|
|
82
|
+
return (
|
|
83
|
+
<TableRow key={day.Day} data-testid={`day-${day.Day}`}>
|
|
84
|
+
<TableCell>{day.Day}</TableCell>
|
|
85
|
+
<TableCell>
|
|
86
|
+
<Typography variant="body2">
|
|
87
|
+
{t(`weather.${day.Weather}Short`)}
|
|
88
|
+
</Typography>
|
|
89
|
+
</TableCell>
|
|
90
|
+
<TableCell align="right">{count(totals.made)}</TableCell>
|
|
91
|
+
<TableCell align="right">{count(totals.sold)}</TableCell>
|
|
92
|
+
<TableCell align="right">{count(totals.pouredAway)}</TableCell>
|
|
93
|
+
<TableCell align="right">{count(totals.turnedAway)}</TableCell>
|
|
94
|
+
<TableCell
|
|
95
|
+
align="right"
|
|
96
|
+
sx={day.Profit === best ? { fontWeight: 700 } : undefined}
|
|
97
|
+
>
|
|
98
|
+
{money(symbol, day.Profit)}
|
|
99
|
+
</TableCell>
|
|
100
|
+
<TableCell align="right">{money(symbol, day.Purse)}</TableCell>
|
|
101
|
+
</TableRow>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</TableBody>
|
|
105
|
+
</Table>
|
|
106
|
+
</Box>
|
|
107
|
+
</Section>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The day's lines, added up across the menu. */
|
|
112
|
+
function totalsOf(results: DayResults) {
|
|
113
|
+
const lines = Object.values(results.Lines);
|
|
114
|
+
return {
|
|
115
|
+
made: lines.reduce((sum, line) => sum + line.Made, 0),
|
|
116
|
+
sold: lines.reduce((sum, line) => sum + line.Sold, 0),
|
|
117
|
+
pouredAway: lines.reduce((sum, line) => sum + line.PouredAway, 0),
|
|
118
|
+
turnedAway: lines.reduce((sum, line) => sum + line.TurnedAway, 0),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Box, Chip, IconButton, Slider, Stack, Typography } from '@mui/material';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
import type { MenuItem } from '../types';
|
|
6
|
+
import { money } from './format';
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
id: string;
|
|
10
|
+
item: MenuItem;
|
|
11
|
+
/** What the game currently holds for this item. */
|
|
12
|
+
value: number;
|
|
13
|
+
/** Roughly how many people will want one today. */
|
|
14
|
+
expected: number;
|
|
15
|
+
max: number;
|
|
16
|
+
step: number;
|
|
17
|
+
currencySymbol: string;
|
|
18
|
+
/** True when this is the item the reader was put in charge of. */
|
|
19
|
+
yours: boolean;
|
|
20
|
+
disabled: boolean;
|
|
21
|
+
onChange: (next: number) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* One line of the menu, and the only thing on this screen that writes.
|
|
26
|
+
*
|
|
27
|
+
* It writes its own key and nothing else, so the person looking after the iced
|
|
28
|
+
* tea and the person looking after the cookies never touch the same field. That
|
|
29
|
+
* is what makes two people working at once safe here, and it is a decision
|
|
30
|
+
* about the shape of the state rather than anything clever in this file.
|
|
31
|
+
*
|
|
32
|
+
* The slider keeps a local value while it is being dragged and writes once, on
|
|
33
|
+
* release. Writing on every frame of a drag would put fifty messages on the
|
|
34
|
+
* wire for one decision, and a broadcast arriving mid-drag would fight the
|
|
35
|
+
* reader's thumb.
|
|
36
|
+
*/
|
|
37
|
+
export default function MakeRow({
|
|
38
|
+
id,
|
|
39
|
+
item,
|
|
40
|
+
value,
|
|
41
|
+
expected,
|
|
42
|
+
max,
|
|
43
|
+
step,
|
|
44
|
+
currencySymbol,
|
|
45
|
+
yours,
|
|
46
|
+
disabled,
|
|
47
|
+
onChange,
|
|
48
|
+
}: Props) {
|
|
49
|
+
const { t } = useTranslation();
|
|
50
|
+
const [dragging, setDragging] = useState(false);
|
|
51
|
+
const [draft, setDraft] = useState(value);
|
|
52
|
+
const draggingRef = useRef(dragging);
|
|
53
|
+
draggingRef.current = dragging;
|
|
54
|
+
|
|
55
|
+
// Follow the game except while this reader is mid-drag, so a team-mate's
|
|
56
|
+
// change to this item still shows up.
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!draggingRef.current) setDraft(value);
|
|
59
|
+
}, [value]);
|
|
60
|
+
|
|
61
|
+
const shown = dragging ? draft : value;
|
|
62
|
+
const nudge = (by: number) => onChange(Math.max(0, Math.min(max, shown + by)));
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Box data-testid={`make-${id}`}>
|
|
66
|
+
<Stack
|
|
67
|
+
direction="row"
|
|
68
|
+
spacing={1}
|
|
69
|
+
sx={{ alignItems: 'baseline', justifyContent: 'space-between' }}
|
|
70
|
+
>
|
|
71
|
+
<Stack direction="row" spacing={1} sx={{ alignItems: 'baseline' }}>
|
|
72
|
+
<Typography variant="subtitle1">{item.Name}</Typography>
|
|
73
|
+
{yours && <Chip size="small" color="primary" label={t('make.yours')} />}
|
|
74
|
+
</Stack>
|
|
75
|
+
<Typography
|
|
76
|
+
variant="h5"
|
|
77
|
+
sx={{ fontVariantNumeric: 'tabular-nums' }}
|
|
78
|
+
data-testid={`made-${id}`}
|
|
79
|
+
>
|
|
80
|
+
{shown}
|
|
81
|
+
</Typography>
|
|
82
|
+
</Stack>
|
|
83
|
+
|
|
84
|
+
<Typography variant="caption" color="text.secondary" component="p">
|
|
85
|
+
{t('make.line', {
|
|
86
|
+
price: money(currencySymbol, item.Price),
|
|
87
|
+
cost: money(currencySymbol, item.Cost),
|
|
88
|
+
expected,
|
|
89
|
+
})}
|
|
90
|
+
</Typography>
|
|
91
|
+
|
|
92
|
+
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
|
93
|
+
<IconButton
|
|
94
|
+
size="small"
|
|
95
|
+
aria-label={t('make.fewer', { name: item.Name })}
|
|
96
|
+
disabled={disabled || shown === 0}
|
|
97
|
+
onClick={() => nudge(-step)}
|
|
98
|
+
>
|
|
99
|
+
<Typography component="span">−</Typography>
|
|
100
|
+
</IconButton>
|
|
101
|
+
<Slider
|
|
102
|
+
aria-label={item.Name}
|
|
103
|
+
value={shown}
|
|
104
|
+
min={0}
|
|
105
|
+
max={max}
|
|
106
|
+
step={1}
|
|
107
|
+
disabled={disabled}
|
|
108
|
+
valueLabelDisplay="auto"
|
|
109
|
+
onChange={(_, next) => {
|
|
110
|
+
setDragging(true);
|
|
111
|
+
setDraft(next as number);
|
|
112
|
+
}}
|
|
113
|
+
onChangeCommitted={(_, next) => {
|
|
114
|
+
setDragging(false);
|
|
115
|
+
onChange(next as number);
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
<IconButton
|
|
119
|
+
size="small"
|
|
120
|
+
aria-label={t('make.more', { name: item.Name })}
|
|
121
|
+
disabled={disabled || shown >= max}
|
|
122
|
+
onClick={() => nudge(step)}
|
|
123
|
+
>
|
|
124
|
+
<Typography component="span">+</Typography>
|
|
125
|
+
</IconButton>
|
|
126
|
+
</Stack>
|
|
127
|
+
</Box>
|
|
128
|
+
);
|
|
129
|
+
}
|