dreamboard 0.1.7 → 0.1.8
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 +89 -0
- package/NOTICE +1 -0
- package/README.md +22 -178
- package/dist/{chunk-MP7IBNWW.js → chunk-BMYC6772.js} +577 -38
- package/dist/chunk-BMYC6772.js.map +1 -0
- package/dist/chunk-D4HDZEJT.js +37 -0
- package/dist/chunk-EM7NOFFC.js +19072 -0
- package/dist/chunk-EM7NOFFC.js.map +1 -0
- package/dist/chunk-SET56RVA.js +435 -0
- package/dist/chunk-SET56RVA.js.map +1 -0
- package/dist/{dist-B3R64F6G.js → dist-WJRJNFLI.js} +3 -3
- package/dist/global-config-VQJZ3JOA.js +15 -0
- package/dist/global-config-VQJZ3JOA.js.map +1 -0
- package/dist/index.js +23993 -24387
- package/dist/index.js.map +1 -1
- package/dist/keychain-backend-2DL3C6BS.js +125 -0
- package/dist/keychain-backend-2DL3C6BS.js.map +1 -0
- package/dist/{prompt-GMZABCJC.js → prompt-IV67HSPU.js} +2 -2
- package/dist/scaffold/assets/static/app/tsconfig.framework.json +24 -0
- package/dist/scaffold/assets/static/app/tsconfig.json +3 -0
- package/dist/scaffold/assets/static/ui/App.tsx +22 -0
- package/dist/scaffold/assets/static/ui/index.tsx +12 -0
- package/dist/scaffold/assets/static/ui/style.css +4 -0
- package/dist/scaffold/assets/static/ui/tsconfig.framework.json +31 -0
- package/dist/scaffold/assets/static/ui/tsconfig.json +5 -0
- package/dist/src-K2NK3A4P.js +626 -0
- package/dist/src-K2NK3A4P.js.map +1 -0
- package/package.json +41 -4
- package/skills/dreamboard/SKILL.md +126 -59
- package/skills/dreamboard/references/authoring-lifecycle.md +102 -0
- package/skills/dreamboard/references/board-surfaces.md +36 -0
- package/skills/dreamboard/references/board-topology.md +443 -0
- package/skills/dreamboard/references/boards-and-topology.md +100 -0
- package/skills/dreamboard/references/card-actions.md +107 -0
- package/skills/dreamboard/references/cli.md +120 -0
- package/skills/dreamboard/references/core-concepts.md +514 -0
- package/skills/dreamboard/references/custom-renderers.md +26 -0
- package/skills/dreamboard/references/derived-values.md +55 -0
- package/skills/dreamboard/references/effects.md +111 -0
- package/skills/dreamboard/references/game-contract.md +89 -0
- package/skills/dreamboard/references/game-definition.md +89 -0
- package/skills/dreamboard/references/game-shell.md +80 -0
- package/skills/dreamboard/references/hand-surfaces.md +33 -0
- package/skills/dreamboard/references/index.md +112 -0
- package/skills/dreamboard/references/inputs-and-targets.md +160 -0
- package/skills/dreamboard/references/interactions.md +158 -0
- package/skills/dreamboard/references/manifest-fields.md +137 -0
- package/skills/dreamboard/references/manifest.md +81 -0
- package/skills/dreamboard/references/package-surfaces.md +69 -0
- package/skills/dreamboard/references/phases.md +145 -0
- package/skills/dreamboard/references/prompts-and-choices.md +55 -0
- package/skills/dreamboard/references/quickstart.md +71 -0
- package/skills/dreamboard/references/rule-authoring.md +81 -77
- package/skills/dreamboard/references/setup-bootstrap.md +78 -0
- package/skills/dreamboard/references/stages-and-zones.md +59 -0
- package/skills/dreamboard/references/static-views.md +67 -0
- package/skills/dreamboard/references/table-queries-and-ops.md +103 -0
- package/skills/dreamboard/references/testing-bases.md +147 -0
- package/skills/dreamboard/references/testing-generated-contracts.md +142 -0
- package/skills/dreamboard/references/testing-runtime-assertions.md +124 -0
- package/skills/dreamboard/references/testing-scenarios.md +148 -0
- package/skills/dreamboard/references/testing-ui-tests.md +174 -0
- package/skills/dreamboard/references/testing.md +161 -0
- package/skills/dreamboard/references/ui-architecture.md +137 -0
- package/skills/dreamboard/references/ui-components.md +34 -0
- package/skills/dreamboard/references/views.md +72 -0
- package/skills/dreamboard/references/workspace-layout.md +136 -0
- package/dist/chunk-2H7UOFLK.js +0 -11
- package/dist/chunk-MP7IBNWW.js.map +0 -1
- package/dist/chunk-WM7TY7DV.js +0 -2328
- package/dist/chunk-WM7TY7DV.js.map +0 -1
- package/dist/embedded-harness-IHP7A2ZX.js +0 -344
- package/dist/embedded-harness-IHP7A2ZX.js.map +0 -1
- package/skills/dreamboard/references/adversarial-testing.md +0 -113
- package/skills/dreamboard/references/all-players-tracking.md +0 -75
- package/skills/dreamboard/references/api-reference.md +0 -197
- package/skills/dreamboard/references/app-best-practices.md +0 -98
- package/skills/dreamboard/references/building-your-first-game.md +0 -49
- package/skills/dreamboard/references/hands-vs-decks.md +0 -124
- package/skills/dreamboard/references/manifest-authoring.md +0 -328
- package/skills/dreamboard/references/phase-handlers.md +0 -134
- package/skills/dreamboard/references/scenario-format.md +0 -99
- package/skills/dreamboard/references/test-harness.md +0 -225
- package/skills/dreamboard/references/ui-best-practices.md +0 -156
- package/skills/dreamboard/references/ui-genre-resource-management.md +0 -187
- package/skills/dreamboard/references/ui-genre-trick-taking.md +0 -110
- package/skills/dreamboard/references/ui-genre-worker-placement.md +0 -143
- package/skills/dreamboard/references/ui-style-guide.md +0 -54
- package/skills/dreamboard/scripts/events-extract.mjs +0 -218
- /package/dist/{chunk-2H7UOFLK.js.map → chunk-D4HDZEJT.js.map} +0 -0
- /package/dist/{dist-B3R64F6G.js.map → dist-WJRJNFLI.js.map} +0 -0
- /package/dist/{prompt-GMZABCJC.js.map → prompt-IV67HSPU.js.map} +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# UI tests
|
|
2
|
+
|
|
3
|
+
Test workspace UI with reducer-native runtime providers.
|
|
4
|
+
|
|
5
|
+
Use `createTestRuntime` for reducer-backed, backend-free React tests that need real
|
|
6
|
+
Dreamboard runtime context. It creates an in-memory runtime from a generated
|
|
7
|
+
base state, drives the same reducer bundle as scenarios, and feeds snapshots
|
|
8
|
+
through the UI host runtime store.
|
|
9
|
+
|
|
10
|
+
This is for UI logic tests, not browser end-to-end tests.
|
|
11
|
+
|
|
12
|
+
## Generate first
|
|
13
|
+
|
|
14
|
+
UI tests depend on generated base states:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
dreamboard test generate
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If the base state is missing or stale, regenerate before debugging React.
|
|
21
|
+
|
|
22
|
+
## Basic harness
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import assert from "node:assert/strict";
|
|
26
|
+
import { test } from "node:test";
|
|
27
|
+
import React from "react";
|
|
28
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
29
|
+
import {
|
|
30
|
+
PluginStateProvider,
|
|
31
|
+
RuntimeProvider,
|
|
32
|
+
useGameView,
|
|
33
|
+
useIsMyTurn,
|
|
34
|
+
} from "@dreamboard/ui-sdk";
|
|
35
|
+
import { useInteractionByKey } from "../shared/generated/ui-contract";
|
|
36
|
+
import { createTestRuntime } from "../test/testing-types";
|
|
37
|
+
|
|
38
|
+
function Probe() {
|
|
39
|
+
const view = useGameView();
|
|
40
|
+
const placeThing = useInteractionByKey("placeThing.placeThingCard");
|
|
41
|
+
const isMyTurn = useIsMyTurn();
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
data-is-my-turn={isMyTurn ? "true" : "false"}
|
|
46
|
+
data-surface={placeThing?.descriptor.surface ?? "missing"}
|
|
47
|
+
data-hand-size={view.handCards.length}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function renderTree(runtime: ReturnType<typeof createTestRuntime>) {
|
|
53
|
+
return renderToStaticMarkup(
|
|
54
|
+
<RuntimeProvider runtime={runtime.runtime}>
|
|
55
|
+
<PluginStateProvider>
|
|
56
|
+
<Probe />
|
|
57
|
+
</PluginStateProvider>
|
|
58
|
+
</RuntimeProvider>,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test("renders the initial playable hand", () => {
|
|
63
|
+
const runtime = createTestRuntime({
|
|
64
|
+
baseId: "initial-turn",
|
|
65
|
+
phase: "placeThing",
|
|
66
|
+
controllingPlayerId: "player-2",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const markup = renderTree(runtime);
|
|
70
|
+
|
|
71
|
+
assert.match(markup, /data-is-my-turn="true"/);
|
|
72
|
+
assert.match(markup, /data-surface="hand"/);
|
|
73
|
+
assert.match(markup, /data-hand-size="5"/);
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The important providers are:
|
|
78
|
+
|
|
79
|
+
| Provider | Purpose |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| `RuntimeProvider` | Supplies the in-memory `RuntimeAPI`. |
|
|
82
|
+
| `PluginStateProvider` | Supplies reducer-projected plugin state to UI hooks. |
|
|
83
|
+
|
|
84
|
+
## Runtime options
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const runtime = createTestRuntime({
|
|
88
|
+
baseId: "initial-turn",
|
|
89
|
+
phase: "placeThing",
|
|
90
|
+
controllingPlayerId: "player-2",
|
|
91
|
+
userId: "test-user",
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Options:
|
|
96
|
+
|
|
97
|
+
| Option | Meaning |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| `baseId` | Generated base state id. Required. |
|
|
100
|
+
| `phase` | Optional expected phase for the initial snapshot. |
|
|
101
|
+
| `controllingPlayerId` | Seat controlled by the rendered UI. |
|
|
102
|
+
| `userId` | Test user id; defaults to `"test-user"`. |
|
|
103
|
+
|
|
104
|
+
`baseId` is typed to generated `BASE_STATES`, so misspelled bases fail at
|
|
105
|
+
compile time.
|
|
106
|
+
|
|
107
|
+
## Submitting interactions
|
|
108
|
+
|
|
109
|
+
`createTestRuntime` returns helpers for driving state:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
const runtime = createTestRuntime({
|
|
113
|
+
baseId: "initial-turn",
|
|
114
|
+
phase: "placeThing",
|
|
115
|
+
controllingPlayerId: "player-2",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await runtime.submit(runtime.seat(1), "placeThingCard", {
|
|
119
|
+
cardId: "a-diamond",
|
|
120
|
+
ringId: "ring-1",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const after = renderTree(runtime);
|
|
124
|
+
assert.match(after, /data-hand-size="4"/);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Submission validates input, dispatches through the reducer bundle, applies a
|
|
128
|
+
fresh gameplay snapshot, and notifies UI subscribers.
|
|
129
|
+
|
|
130
|
+
## Switching seats
|
|
131
|
+
|
|
132
|
+
Use `setControllingPlayer` to test player-specific views:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
runtime.setControllingPlayer("player-3");
|
|
136
|
+
|
|
137
|
+
const markup = renderTree(runtime);
|
|
138
|
+
assert.match(markup, /data-is-my-turn="false"/);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This exercises the same session-store path used by host player switching.
|
|
142
|
+
|
|
143
|
+
## What to test here
|
|
144
|
+
|
|
145
|
+
Good UI runtime tests:
|
|
146
|
+
|
|
147
|
+
- hooks see the expected phase, view, and available interactions
|
|
148
|
+
- `useIsMyTurn` and controlling-player changes behave correctly
|
|
149
|
+
- custom primitive renderers receive descriptors
|
|
150
|
+
- card hands update after reducer submissions
|
|
151
|
+
- prompt or blocker UI appears for the addressed seat only
|
|
152
|
+
|
|
153
|
+
Avoid testing browser-only layout here. Use the local dev host or browser runner
|
|
154
|
+
for screenshots, pointer events, and CSS layout issues.
|
|
155
|
+
|
|
156
|
+
## Projection fixture tests
|
|
157
|
+
|
|
158
|
+
For lighter UI probes, read generated base projection JSON and provide a small
|
|
159
|
+
mock `RuntimeAPI`. This is useful when the test only needs hooks and render
|
|
160
|
+
behavior, not reducer dispatch.
|
|
161
|
+
|
|
162
|
+
Star Settlers uses this pattern in `test/ui/app.test.tsx`: it loads
|
|
163
|
+
`test/generated/bases/<base>/player-*.projection.json`, wraps probes in
|
|
164
|
+
`RuntimeProvider` and `PluginStateProvider`, then asserts that typed hooks such
|
|
165
|
+
as `useGameView`, `useInteractionByKey`, and `useBoardInteractions` see the
|
|
166
|
+
expected projected state.
|
|
167
|
+
|
|
168
|
+
## TSX runtime config
|
|
169
|
+
|
|
170
|
+
Reducer-native UI tests that import React components from workspace or SDK
|
|
171
|
+
sources may need a TSX-specific test tsconfig that enables the React JSX
|
|
172
|
+
transform for those sources. Keep that config separate from the normal
|
|
173
|
+
`test/tsconfig.json`; broadening the normal typecheck include can pull SDK
|
|
174
|
+
fixtures into `tsc --noEmit`.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Testing overview
|
|
2
|
+
|
|
3
|
+
Generate and run Dreamboard reducer-native tests.
|
|
4
|
+
|
|
5
|
+
Dreamboard tests run authored scenarios against the
|
|
6
|
+
same reducer contract that powers the game, using generated workspace types for
|
|
7
|
+
player ids, interaction ids, params, phases, views, and assertion helpers.
|
|
8
|
+
|
|
9
|
+
Use tests for repeatable gameplay coverage:
|
|
10
|
+
|
|
11
|
+
- opening-state invariants
|
|
12
|
+
- setup checkpoints reused by many scenarios
|
|
13
|
+
- happy paths across a turn, round, or full game
|
|
14
|
+
- rejection paths for illegal actors or params
|
|
15
|
+
- prompt recipient and availability checks
|
|
16
|
+
- UI component probes against reducer-projected plugin state
|
|
17
|
+
|
|
18
|
+
## Workspace layout
|
|
19
|
+
|
|
20
|
+
| Path | Ownership | Purpose |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `test/bases/*.base.ts` | Authored | Reusable seeded starting states via `defineBase`. |
|
|
23
|
+
| `test/scenarios/*.scenario.ts` | Authored | Reducer-native scenario files via `defineScenario`. |
|
|
24
|
+
| `test/ui/*.test.tsx` | Authored | Optional UI tests using `createTestRuntime`. |
|
|
25
|
+
| `test/testing-types.ts` | Generated | Workspace-narrowed test import surface. |
|
|
26
|
+
| `test/generated/*` | Generated | Base snapshots, testing contract, scenario manifest, projections, snapshots, and metadata. |
|
|
27
|
+
|
|
28
|
+
Import test helpers from `../testing-types`. Do not import directly from
|
|
29
|
+
`test/generated/*` and do not edit generated files by hand.
|
|
30
|
+
|
|
31
|
+
## Command flow
|
|
32
|
+
|
|
33
|
+
Generate or refresh reducer-native artifacts:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
dreamboard test generate
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Run all reducer scenarios:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
dreamboard test run
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Run one scenario file:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
dreamboard test run --scenario test/scenarios/trade-full-lifecycle.scenario.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Refresh scenario snapshots while running:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
dreamboard test run --update-snapshots
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Public workspaces should normally omit `--runner`. The default runner is the
|
|
58
|
+
reducer runner. Internal source-checkout lanes may also use `embedded` or
|
|
59
|
+
`browser`, but those are parity lanes rather than the public authoring loop.
|
|
60
|
+
|
|
61
|
+
Example workspaces may also expose package-local checks. Star Settlers, for
|
|
62
|
+
example, uses `pnpm typecheck:test` for test TypeScript and `pnpm test:ui` for
|
|
63
|
+
React UI probes under `test/ui`.
|
|
64
|
+
|
|
65
|
+
## What generate does
|
|
66
|
+
|
|
67
|
+
`dreamboard test generate`:
|
|
68
|
+
|
|
69
|
+
1. Ensures `test/testing-types.ts` and `test/generated/testing-contract.ts`
|
|
70
|
+
exist and match the current workspace contract.
|
|
71
|
+
2. Loads `test/bases/*.base.ts` and `test/scenarios/*.scenario.ts`.
|
|
72
|
+
3. Validates that every scenario references an existing base.
|
|
73
|
+
4. Executes base setup against the reducer to produce deterministic base
|
|
74
|
+
snapshots.
|
|
75
|
+
5. Writes generated artifacts under `test/generated/`.
|
|
76
|
+
|
|
77
|
+
Regenerate after changes that affect runtime or type shape:
|
|
78
|
+
|
|
79
|
+
- `manifest.ts`
|
|
80
|
+
- `app/game.ts`
|
|
81
|
+
- phase files, interactions, card actions, targets, or views
|
|
82
|
+
- setup profiles
|
|
83
|
+
- generated `shared/generated/ui-contract.ts`
|
|
84
|
+
- stale base fingerprints reported by `dreamboard test run`
|
|
85
|
+
|
|
86
|
+
## What run does
|
|
87
|
+
|
|
88
|
+
`dreamboard test run`:
|
|
89
|
+
|
|
90
|
+
1. Ensures generated testing files exist.
|
|
91
|
+
2. Loads bases, scenarios, generated base states, manifest, and game reducer.
|
|
92
|
+
3. Checks generated base fingerprints against the current workspace.
|
|
93
|
+
4. Starts each scenario from its base.
|
|
94
|
+
5. Runs `when`.
|
|
95
|
+
6. Checks optional `phase` and `stage`.
|
|
96
|
+
7. Runs `then`.
|
|
97
|
+
8. Prints one pass or fail line per scenario plus a summary.
|
|
98
|
+
|
|
99
|
+
If the base artifact is stale, the runner fails before pretending the scenario
|
|
100
|
+
is meaningful. Regenerate instead of debugging against an old snapshot.
|
|
101
|
+
|
|
102
|
+
## Minimal base and scenario
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// test/bases/initial-turn.base.ts
|
|
106
|
+
import { defineBase } from "../testing-types";
|
|
107
|
+
|
|
108
|
+
export default defineBase({
|
|
109
|
+
id: "initial-turn",
|
|
110
|
+
seed: 1337,
|
|
111
|
+
players: 4,
|
|
112
|
+
setup: async () => undefined,
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// test/scenarios/smoke-initial-turn.scenario.ts
|
|
118
|
+
import { defineScenario } from "../testing-types";
|
|
119
|
+
|
|
120
|
+
export default defineScenario({
|
|
121
|
+
id: "smoke-initial-turn",
|
|
122
|
+
from: "initial-turn",
|
|
123
|
+
phase: "setup",
|
|
124
|
+
when: async () => undefined,
|
|
125
|
+
then: ({ state, interactions, expect, seat }) => {
|
|
126
|
+
expect(state()).toBe("setup");
|
|
127
|
+
expect(interactions(seat(0))).toHaveInteraction("placeSetupMarker");
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Use `seat(0)`, `seat(1)`, and `players()` instead of hard-coded `"player-1"`
|
|
133
|
+
strings. This keeps tests portable across player counts and generated player id
|
|
134
|
+
unions.
|
|
135
|
+
|
|
136
|
+
## Recommended coverage
|
|
137
|
+
|
|
138
|
+
Every game should have:
|
|
139
|
+
|
|
140
|
+
- one opening-state scenario
|
|
141
|
+
- one normal turn or round scenario
|
|
142
|
+
- one scenario that crosses a phase boundary
|
|
143
|
+
- one rejection scenario for out-of-turn or invalid params
|
|
144
|
+
- one prompt recipient scenario if the game uses prompts
|
|
145
|
+
- one setup-profile or setup-completion scenario when setup is non-trivial
|
|
146
|
+
|
|
147
|
+
For larger games, create bases for expensive checkpoints such as post-setup,
|
|
148
|
+
mid-game economy, pending trade, pending combat, or near-endgame. Do not make
|
|
149
|
+
one scenario replay a full game just to test a late rule.
|
|
150
|
+
|
|
151
|
+
## Split pages
|
|
152
|
+
|
|
153
|
+
- [Bases](./testing-bases.md) explains seeded checkpoints and inheritance.
|
|
154
|
+
- [Scenarios](./testing-scenarios.md) explains `defineScenario` and scenario
|
|
155
|
+
flow.
|
|
156
|
+
- [Runtime assertions](./testing-runtime-assertions.md) lists context helpers
|
|
157
|
+
and matchers.
|
|
158
|
+
- [UI tests](./testing-ui-tests.md) shows `createTestRuntime` with React
|
|
159
|
+
providers.
|
|
160
|
+
- [Generated testing contracts](./testing-generated-contracts.md) explains
|
|
161
|
+
generated files and stale-artifact failures.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# UI architecture
|
|
2
|
+
|
|
3
|
+
Understand the generated UI contract, UI SDK, and host runtime boundaries.
|
|
4
|
+
|
|
5
|
+
Dreamboard UI is reducer-driven and component-first. The reducer projects the
|
|
6
|
+
current player's view and available interactions; React composes generated
|
|
7
|
+
primitives, package defaults, and local visuals around those authoritative
|
|
8
|
+
descriptors.
|
|
9
|
+
|
|
10
|
+
Do not reimplement legality in React. UI code may choose layout, copy,
|
|
11
|
+
highlights, and controls, but availability, eligible targets, prompt options,
|
|
12
|
+
and submitted param shapes should come from authored reducer contracts and
|
|
13
|
+
generated types.
|
|
14
|
+
|
|
15
|
+
## Layers
|
|
16
|
+
|
|
17
|
+
| Layer | Import path | Owns |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| Workspace UI contract | `@dreamboard/ui-contract` | Typed `Interaction`, `Prompt`, `PromptInbox`, `Zone`, `Board`, generated hooks, interaction keys, input keys, zone ids, card ids, phase names, and board targets. |
|
|
20
|
+
| UI SDK | `@dreamboard/ui-sdk` | Runtime providers, generic hooks, visual primitives such as `Card`, `HexGrid`, `ResourceCounter`, and advanced escape-hatch hooks. |
|
|
21
|
+
| UI defaults | `@dreamboard/ui-sdk/defaults` | Styled wrappers over primitives such as `DefaultPromptInbox`, `DefaultInteractionList`, `DefaultZone`, and `GameLayout`. |
|
|
22
|
+
| Host runtime | Provided by the dev/play host | Session state, state-sync snapshots, interaction submission, validation RPCs, player switching, history restore, and host-only feedback. |
|
|
23
|
+
|
|
24
|
+
Most authored UI imports the generated contract first:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import {
|
|
28
|
+
Interaction,
|
|
29
|
+
PromptInbox,
|
|
30
|
+
Zone,
|
|
31
|
+
useBoardInteractions,
|
|
32
|
+
useGameView,
|
|
33
|
+
useInteractionByKey,
|
|
34
|
+
} from "@dreamboard/ui-contract";
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use `@dreamboard/ui-sdk` for framework primitives that are not
|
|
38
|
+
workspace-specific:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { Card, HexGrid, ResourceCounter, ToastProvider } from "@dreamboard/ui-sdk";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`@dreamboard/ui-contract` is a generated workspace alias backed by
|
|
45
|
+
`shared/generated/ui-contract.ts`, not a standalone npm package. The generated
|
|
46
|
+
file augments `@dreamboard/ui-sdk`'s `DreamboardUIRegister`, calls
|
|
47
|
+
`createDreamboardUI(uiContract)`, and exports typed primitives such as
|
|
48
|
+
`Interaction`, `Prompt`, `PromptInbox`, `PromptDialogHost`, `PlayerRoster`,
|
|
49
|
+
`Phase`, `Zone`, and `Board`.
|
|
50
|
+
|
|
51
|
+
## Data flow
|
|
52
|
+
|
|
53
|
+
1. Authoring files declare manifest objects, phases, interactions, inputs,
|
|
54
|
+
targets, card actions, and views.
|
|
55
|
+
2. `dreamboard sync` generates workspace contracts, including
|
|
56
|
+
`@dreamboard/ui-contract`.
|
|
57
|
+
3. The trusted bundle evaluates the current seat and projects descriptors into
|
|
58
|
+
plugin state.
|
|
59
|
+
4. UI primitives read those descriptors and expose state through props,
|
|
60
|
+
contexts, `data-*` attributes, and typed handles.
|
|
61
|
+
5. UI submits through `Interaction.Submit`, `Prompt.Option`,
|
|
62
|
+
`InteractionHandle`, or generated board target helpers.
|
|
63
|
+
|
|
64
|
+
The descriptor is a behavior contract, not a presentation DSL. It carries ids,
|
|
65
|
+
availability, unavailable reasons, input domains, target eligibility, prompt
|
|
66
|
+
options, zone metadata, and phase metadata. Visual policy belongs in React/CSS
|
|
67
|
+
or default wrappers.
|
|
68
|
+
|
|
69
|
+
## Primitive scaffold
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { PromptInbox } from "@dreamboard/ui-contract";
|
|
73
|
+
|
|
74
|
+
export default function App() {
|
|
75
|
+
return (
|
|
76
|
+
<main>
|
|
77
|
+
<PromptInbox.Root>
|
|
78
|
+
<PromptInbox.Empty>No available prompts.</PromptInbox.Empty>
|
|
79
|
+
<PromptInbox.Items />
|
|
80
|
+
</PromptInbox.Root>
|
|
81
|
+
</main>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use the same pattern for actions and zones:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<Interaction.Root interaction="playerTurn.buyCard">
|
|
90
|
+
<Interaction.Label />
|
|
91
|
+
<Interaction.Submit>Buy</Interaction.Submit>
|
|
92
|
+
</Interaction.Root>
|
|
93
|
+
|
|
94
|
+
<Zone.Root zone="hand">
|
|
95
|
+
<Zone.List>
|
|
96
|
+
{view.hand.map((card) => (
|
|
97
|
+
<Zone.Item key={card.id} card={card.id}>
|
|
98
|
+
<Interaction.CardInput input="cardId" card={card.id}>
|
|
99
|
+
<Card card={card} />
|
|
100
|
+
</Interaction.CardInput>
|
|
101
|
+
</Zone.Item>
|
|
102
|
+
))}
|
|
103
|
+
</Zone.List>
|
|
104
|
+
</Zone.Root>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Custom board escape hatch
|
|
108
|
+
|
|
109
|
+
SVG or canvas-heavy boards can use generated `useBoardInteractions()` while the
|
|
110
|
+
reducer still owns eligibility:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
const board = useBoardInteractions();
|
|
114
|
+
|
|
115
|
+
<HexGrid
|
|
116
|
+
board={view.board}
|
|
117
|
+
interactiveVertices={board.targetLayers.vertex()}
|
|
118
|
+
interactiveEdges={board.targetLayers.edge()}
|
|
119
|
+
renderTile={(tile, geometry) => <polygon points={geometry.points()} />}
|
|
120
|
+
renderEdge={(edge) => <line />}
|
|
121
|
+
renderVertex={(vertex) => <circle /> /* sketch only */}
|
|
122
|
+
/>;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Boundaries
|
|
126
|
+
|
|
127
|
+
| Put it here | Examples |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| Reducer authoring | Turn order, actor rules, prompt recipients, target eligibility, costs, validation, state transitions. |
|
|
130
|
+
| Generated UI contract | Interaction keys, input names, prompt options, phase-qualified param types, zones, card ids, board target ids. |
|
|
131
|
+
| Authored React UI | Layout, styling, board geometry, card faces, component composition, selected draft inputs. |
|
|
132
|
+
| Host runtime | Player switching, session chrome, state-sync, submission transport, host-only notifications. |
|
|
133
|
+
|
|
134
|
+
Do not mirror host-only concepts into authored reducer state just to make UI
|
|
135
|
+
chrome easier. Do not copy target predicates into React just to enable or
|
|
136
|
+
disable a board element. If React needs a highlight, expose it through a target
|
|
137
|
+
collector or derived view.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# UI components
|
|
2
|
+
|
|
3
|
+
Generic presentational components available from @dreamboard/ui-sdk.
|
|
4
|
+
|
|
5
|
+
Dreamboard ships generic presentational components in `@dreamboard/ui-sdk`.
|
|
6
|
+
They are not game-specific; pair them with generated primitives from
|
|
7
|
+
`@dreamboard/ui-contract` when behavior needs typed interactions.
|
|
8
|
+
|
|
9
|
+
| Component | Use |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `Card` | Render a card face from projected view data. |
|
|
12
|
+
| `Hand` | Presentational card row/fan helper. |
|
|
13
|
+
| `HexGrid` / `SquareGrid` | Board geometry renderers. |
|
|
14
|
+
| `ResourceCounter` / `CostDisplay` | Resource and cost display. |
|
|
15
|
+
| `DiceRoller` | Dice display plus optional roll action handle. |
|
|
16
|
+
| `PlayerRoster` | Generated player roster primitive for player chrome. |
|
|
17
|
+
| `GameEndDisplay` | End-of-game summary. |
|
|
18
|
+
| `ToastProvider` | Local toast context for UI feedback. |
|
|
19
|
+
|
|
20
|
+
Behavior comes from generated primitives:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { Interaction, Zone } from "@dreamboard/ui-contract";
|
|
24
|
+
import { Card } from "@dreamboard/ui-sdk";
|
|
25
|
+
|
|
26
|
+
<Zone.Item card={card.id}>
|
|
27
|
+
<Interaction.CardInput input="cardId" card={card.id}>
|
|
28
|
+
<Card card={card} />
|
|
29
|
+
</Interaction.CardInput>
|
|
30
|
+
</Zone.Item>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Presentational components should render projected view data or interaction
|
|
34
|
+
handles. They should not inspect reducer internals or recompute legality.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Views
|
|
2
|
+
|
|
3
|
+
Use defineView to project dynamic player-facing state for UI and clients.
|
|
4
|
+
|
|
5
|
+
Views are the dynamic player-facing projection boundary. The UI reads a view; it should not inspect raw reducer state.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
export const playerView = defineView<GameContract>()({
|
|
9
|
+
project({ state, playerId, q }) {
|
|
10
|
+
const scores = state.publicState.scores;
|
|
11
|
+
const handCards = q.zone.playerCards(playerId, "things-hand").map((cardId) => {
|
|
12
|
+
const card = q.card.get(cardId);
|
|
13
|
+
return {
|
|
14
|
+
id: cardId,
|
|
15
|
+
label: card.name ?? cardId,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
phase: state.flow.currentPhase,
|
|
21
|
+
myScore: scores[playerId] ?? 0,
|
|
22
|
+
scores,
|
|
23
|
+
handCards,
|
|
24
|
+
winnerPlayerId: state.publicState.winnerPlayerId,
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Register views in `defineGame`:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
export default defineGame({
|
|
34
|
+
contract: gameContract,
|
|
35
|
+
phases,
|
|
36
|
+
views: {
|
|
37
|
+
player: playerView,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The generated UI contract exposes the projected type as `GameView` and `InferView<"player">`.
|
|
43
|
+
|
|
44
|
+
## What a view can read
|
|
45
|
+
|
|
46
|
+
`project` receives:
|
|
47
|
+
|
|
48
|
+
- `state`, including public/private/hidden/phase/table data as appropriate for the runtime projection
|
|
49
|
+
- `playerId`
|
|
50
|
+
- `q`, the typed table query namespace
|
|
51
|
+
- `derived`, for memoized derived values
|
|
52
|
+
- runtime context such as current phase, player order, active players, and setup
|
|
53
|
+
|
|
54
|
+
Use `q` for table facts such as cards in a hand, board occupants, resource balances, and component locations.
|
|
55
|
+
|
|
56
|
+
## What belongs in a view
|
|
57
|
+
|
|
58
|
+
Views should shape data for UI consumption:
|
|
59
|
+
|
|
60
|
+
- labels for cards or pieces
|
|
61
|
+
- counts and summaries
|
|
62
|
+
- player-specific hand information
|
|
63
|
+
- current phase and step display data
|
|
64
|
+
- safe public slices of score and winner state
|
|
65
|
+
|
|
66
|
+
Views should not run rule validation or decide legal moves. The runtime projects interactions and eligible targets from reducer definitions.
|
|
67
|
+
|
|
68
|
+
## Static vs dynamic
|
|
69
|
+
|
|
70
|
+
Use `defineView` for data that can change during a session. Use [Static views](./static-views.md) for manifest-derived payloads that do not change after session initialization.
|
|
71
|
+
|
|
72
|
+
Avoid putting large static topology in every dynamic view update.
|