@dreamboard-games/workspace-codegen 0.1.2 → 0.1.4

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/src/seeds.ts CHANGED
@@ -14,9 +14,9 @@ import {
14
14
  createClientParamSchemasByPhase,
15
15
  } from "@dreamboard/app-sdk/reducer";
16
16
  import type {
17
- ClientParamsOfInteractionOfDefinition,
18
- DefaultedClientParamKeysOfInteractionOfDefinition,
19
- InteractionIdOfDefinition,
17
+ ClientParamsOfInteractionOfDefinition,
18
+ DefaultedClientParamKeysOfInteractionOfDefinition,
19
+ InteractionIdOfDefinition,
20
20
  InteractionIdOfDefinitionPhase,
21
21
  PhaseNamesOfDefinition,
22
22
  StageNamesOfDefinitionPhase,
@@ -24,35 +24,38 @@ import type {
24
24
  ViewOfDefinition,
25
25
  } from "@dreamboard/app-sdk/reducer";
26
26
  import {
27
- createDreamboardUI,
28
- createHexBoardView,
29
- InteractionField as InteractionFieldGeneric,
30
- InteractionForm as InteractionFormGeneric,
31
- useActivePlayers as useActivePlayersGeneric,
32
- useBoardInteractions as useBoardInteractionsGeneric,
33
- useGameView as useGameViewGeneric,
34
- useInteractionByKey as useInteractionByKeyGeneric,
35
- usePlayerTurnOrder as usePlayerTurnOrderGeneric,
36
- type BoardInteractionsContext,
37
- type BoardInteractionsOptions,
38
- type BoardSpaceIdOf,
39
- type ClientParamSchemaMap,
40
- type HexBoardView,
41
- type InteractionDescriptor,
42
- type InteractionFieldProps as InteractionFieldPropsGeneric,
43
- type InteractionFieldRenderMap,
44
- type InteractionFormProps as InteractionFormPropsGeneric,
45
- type InteractionHandle,
46
- type DreamboardUI,
47
- type UIContract,
48
- } from "@dreamboard/ui-sdk";
49
- import { createElement, useMemo, type ReactElement } from "react";
50
- import {
51
- literals,
52
- staticBoards,
53
- type CardId,
54
- type EdgeId,
27
+ createDreamboardUI,
28
+ createResourceCounter,
29
+ type BoardHexGridProps as BoardHexGridPropsGeneric,
30
+ type BoardHexViewProps as BoardHexViewPropsGeneric,
31
+ type BoardSpaceIdOf,
32
+ type ClientParamSchemaMap,
33
+ type InteractionDescriptor,
34
+ type InteractionHandle,
35
+ type InteractionRouteMap as InteractionRouteMapGeneric,
36
+ type InteractionSwitchProps as InteractionSwitchPropsGeneric,
37
+ type InteractionSwitchRenderState,
38
+ type DreamboardUI,
39
+ type ResourceCounterComponents,
40
+ type ResourceDisplayConfig,
41
+ type TypedGame,
42
+ type UIContract,
43
+ type UIRootProps,
44
+ type ZoneCardAtProps,
45
+ type ZoneCardRenderItem,
46
+ type ZoneListProps,
47
+ type ZonePileCardsProps,
48
+ } from "@dreamboard/ui-sdk";
49
+ import { createElement, type ReactElement, type ReactNode } from "react";
50
+ import {
51
+ literals,
52
+ staticBoards,
53
+ type CardId,
54
+ type CardProperties,
55
+ type CardType,
56
+ type EdgeId,
55
57
  type PlayerId,
58
+ type ResourceId,
56
59
  type SpaceId,
57
60
  type VertexId,
58
61
  type ZoneId as ManifestZoneId,
@@ -151,8 +154,7 @@ export type InteractionDescriptorFor<Key extends InteractionKey> =
151
154
 
152
155
  /**
153
156
  * Params shape for a phase-qualified interaction key. Drives strong typing
154
- * for \`useInteractionByKey\`'s draft,
155
- * \`submit\`, and \`setInput\`.
157
+ * for component-first interaction state, forms, and submits.
156
158
  */
157
159
  export type InteractionParamsOf<Key extends InteractionKey> =
158
160
  InteractionParams<PhaseOfInteractionKey<Key>, IdOfInteractionKey<Key>>;
@@ -175,6 +177,50 @@ type InteractionHandleDefaultedKeys<Key extends InteractionKey> = Extract<
175
177
  keyof InteractionParamsShape<Key> & string
176
178
  >;
177
179
 
180
+ type RequiredInteractionInputKeysOf<Key extends InteractionKey> = Exclude<
181
+ InteractionInputKeysOf<Key>,
182
+ InteractionHandleDefaultedKeys<Key>
183
+ >;
184
+
185
+ export type RequiredInteractionInputKey<Key extends InteractionKey> =
186
+ RequiredInteractionInputKeysOf<Key>;
187
+
188
+ export type ZeroInputInteractionKey = {
189
+ [K in InteractionKey]: InteractionInputKeysOf<K> extends never
190
+ ? K
191
+ : never;
192
+ }[InteractionKey];
193
+
194
+ export type InputInteractionKey = Exclude<
195
+ InteractionKey,
196
+ ZeroInputInteractionKey
197
+ >;
198
+
199
+ export type InteractionRouteRenderState<Key extends InteractionKey> =
200
+ Omit<InteractionSwitchRenderState<Key>, "descriptor"> & {
201
+ descriptor: InteractionDescriptorFor<Key>;
202
+ };
203
+
204
+ export type InteractionRouteMap = {
205
+ [K in InteractionKey]: (state: InteractionRouteRenderState<K>) => ReactNode;
206
+ };
207
+
208
+ type ExactInteractionRoutes<Routes extends InteractionRouteMap> = Routes &
209
+ Record<Exclude<keyof Routes, keyof InteractionRouteMap>, never>;
210
+
211
+ export function defineInteractionRoutes<const Routes extends InteractionRouteMap>(
212
+ routes: ExactInteractionRoutes<Routes>,
213
+ ): InteractionRouteMap {
214
+ return routes;
215
+ }
216
+
217
+ type InteractionSwitchProps = Omit<
218
+ InteractionSwitchPropsGeneric<InteractionKey>,
219
+ "routes"
220
+ > & {
221
+ routes: InteractionRouteMap;
222
+ };
223
+
178
224
  type InteractionInputKeyOf<Key extends InteractionKey> =
179
225
  Key extends InteractionKey ? InteractionInputKeysOf<Key> : never;
180
226
 
@@ -206,6 +252,10 @@ type UIPromptOptionRegistry = {
206
252
  [K in PromptOptionValue]: { value: K };
207
253
  };
208
254
 
255
+ type UIPlayerRegistry = {
256
+ [K in PlayerId & string]: { player: K };
257
+ };
258
+
209
259
  type UIZoneRegistry = {
210
260
  [K in ZoneId & string]: { zone: K };
211
261
  };
@@ -227,6 +277,7 @@ export const uiContract = {
227
277
  inputs: {} as UIInputRegistry,
228
278
  prompts: {} as UIPromptRegistry,
229
279
  promptOptions: {} as UIPromptOptionRegistry,
280
+ players: {} as UIPlayerRegistry,
230
281
  zones: {} as UIZoneRegistry,
231
282
  cards: {} as UICardRegistry,
232
283
  phases: {} as UIPhaseRegistry,
@@ -240,142 +291,6 @@ declare module "@dreamboard/ui-sdk" {
240
291
  }
241
292
  }
242
293
 
243
- type WorkspaceUI = DreamboardUI<typeof uiContract>;
244
-
245
- export const UI: WorkspaceUI = createDreamboardUI(uiContract);
246
- export const Interaction = UI.Interaction;
247
- export const Prompt = UI.Prompt;
248
- export const PromptInbox = UI.PromptInbox;
249
- export const PromptDialogHost = UI.PromptDialogHost;
250
- export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
251
- export const Phase = UI.Phase;
252
- export const Zone = UI.Zone;
253
- export const Board = UI.Board;
254
-
255
- export type InteractionFieldRenderers<Key extends InteractionKey> =
256
- InteractionFieldRenderMap<InteractionParamsShape<Key>>;
257
-
258
- export type InteractionFormProps<Key extends InteractionKey> = Omit<
259
- InteractionFormPropsGeneric<
260
- InteractionParamsShape<Key>,
261
- InteractionHandleDefaultedKeys<Key>
262
- >,
263
- "descriptor" | "handle"
264
- > & {
265
- descriptor: InteractionDescriptorFor<Key>;
266
- handle: InteractionHandle<
267
- InteractionParamsShape<Key>,
268
- InteractionHandleDefaultedKeys<Key>
269
- >;
270
- renderFields?: InteractionFieldRenderers<Key>;
271
- };
272
-
273
- export type InteractionFieldProps<
274
- Key extends InteractionKey,
275
- InputKey extends keyof InteractionParamsShape<Key> & string,
276
- > = Omit<
277
- InteractionFieldPropsGeneric<InteractionParamsShape<Key>, InputKey>,
278
- "descriptor" | "handle"
279
- > & {
280
- descriptor: InteractionDescriptorFor<Key>;
281
- handle: InteractionHandle<
282
- InteractionParamsShape<Key>,
283
- InteractionHandleDefaultedKeys<Key>
284
- >;
285
- };
286
-
287
- export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
288
- game,
289
- ) as ClientParamSchemaMap;
290
-
291
- /**
292
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s generic
293
- * \`useInteractionByKey\`. The \`key\` argument is constrained to the
294
- * generated {@link InteractionKey} union (typos are compile errors), and
295
- * the returned {@link InteractionHandle} is parameterised on
296
- * {@link InteractionParamsOf} so \`handle.draft\`, \`handle.submit\`, and
297
- * \`handle.setInput\` are statically typed against the interaction's
298
- * declared inputs.
299
- *
300
- * \`\`\`ts
301
- * const handle = useInteractionByKey("play.placeThingCard");
302
- * if (!handle) return null;
303
- * handle.setInput("cardId", card.id); // inferred as ThingsDeckCardId
304
- * await handle.submit(); // params typed from the reducer contract
305
- * \`\`\`
306
- */
307
- export function useInteractionByKey<Key extends InteractionKey>(
308
- key: Key | null | undefined,
309
- ): InteractionHandle<
310
- InteractionParamsShape<Key>,
311
- InteractionHandleDefaultedKeys<Key>
312
- > | null {
313
- return useInteractionByKeyGeneric<
314
- Key,
315
- InteractionParamsShape<Key>,
316
- InteractionHandleDefaultedKeys<Key>
317
- >(key);
318
- }
319
-
320
- export function InteractionForm<Key extends InteractionKey>(
321
- props: InteractionFormProps<Key>,
322
- ): ReactElement {
323
- return createElement(
324
- InteractionFormGeneric<
325
- InteractionParamsShape<Key>,
326
- InteractionHandleDefaultedKeys<Key>
327
- >,
328
- props,
329
- );
330
- }
331
-
332
- export function InteractionField<
333
- Key extends InteractionKey,
334
- InputKey extends keyof InteractionParamsShape<Key> & string,
335
- >(props: InteractionFieldProps<Key, InputKey>): ReactElement | null {
336
- return createElement(
337
- InteractionFieldGeneric<InteractionParamsShape<Key>, InputKey>,
338
- props,
339
- );
340
- }
341
-
342
- /**
343
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s
344
- * \`useBoardInteractions\`. Returns a {@link BoardInteractionsContext}
345
- * narrowed to this workspace's board-interaction union for exhaustive
346
- * downstream logic.
347
- *
348
- * \`\`\`tsx
349
- * const board = useBoardInteractions();
350
- * <HexGrid interactiveVertices={board.targetLayers.vertex()} />
351
- * \`\`\`
352
- *
353
- * Reach for this whenever a screen keeps more than one board target
354
- * interaction live simultaneously and dispatch is target-driven instead of
355
- * armed-then-clicked. Ambiguous unarmed overlaps should be resolved in the UI
356
- * by arming an explicit interaction/input route.
357
- */
358
- export function useBoardInteractions(
359
- options?: BoardInteractionsOptions,
360
- ): BoardInteractionsContext<BoardInteractions> {
361
- return useBoardInteractionsGeneric<BoardInteractions>(options);
362
- }
363
-
364
- /** Workspace-typed active-player hook. */
365
- export function useActivePlayers(): readonly PlayerId[] {
366
- return useActivePlayersGeneric() as readonly PlayerId[];
367
- }
368
-
369
- /** Workspace-typed reducer-native player view hook. */
370
- export function useGameView(): GameView {
371
- return useGameViewGeneric() as GameView;
372
- }
373
-
374
- /** Workspace-typed player turn order hook. */
375
- export function usePlayerTurnOrder(): readonly PlayerId[] {
376
- return usePlayerTurnOrderGeneric() as readonly PlayerId[];
377
- }
378
-
379
294
  // -------------------------------------------------------------------------
380
295
  // Typed hex-board view adapter
381
296
  // -------------------------------------------------------------------------
@@ -394,50 +309,177 @@ export type HexBoardSpaceId<Id extends HexBoardId> = BoardSpaceIdOf<
394
309
  HexBoardTopology<Id>
395
310
  >;
396
311
 
397
- /**
398
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s
399
- * \`createHexBoardView\`. Joins the static topology of the named hex
400
- * board with a per-space view overlay and returns a value ready for
401
- * \`<HexGrid board={...} />\`. Each rendered tile carries a \`view\`
402
- * field typed as the matched overlay row.
403
- *
404
- * Strict by construction: every static space must have exactly one
405
- * overlay; missing, duplicate, or unknown overlay ids throw.
406
- *
407
- * \`\`\`tsx
408
- * const island = useHexBoardView("island", { spaces: view.spaces });
409
- *
410
- * <HexGrid
411
- * board={island}
412
- * renderTile={(tile) => {
413
- * const terrain = tile.view.terrain;
414
- * // ...
415
- * }}
416
- * />
417
- * \`\`\`
418
- */
419
- export function useHexBoardView<
420
- const Id extends HexBoardId,
421
- const TSpaceView extends { id: HexBoardSpaceId<Id> },
422
- >(
423
- boardId: Id,
424
- options: { spaces: ReadonlyArray<TSpaceView> },
425
- ): HexBoardView<HexBoardTopology<Id>, TSpaceView> {
426
- const board = hexStaticBoards[boardId];
427
- const { spaces } = options;
428
- return useMemo(
429
- () => createHexBoardView<HexBoardTopology<Id>, TSpaceView>(board, { spaces }),
430
- [board, spaces],
431
- );
432
- }
312
+ export type HexBoardViewProps<
313
+ Id extends HexBoardId,
314
+ TSpaceView extends { id: HexBoardSpaceId<Id> },
315
+ > = Omit<BoardHexViewPropsGeneric<HexBoardTopology<Id>, TSpaceView>, "board"> & {
316
+ board: Id;
317
+ };
318
+
319
+ export type HexBoardGridProps<
320
+ Id extends HexBoardId,
321
+ TSpaceView extends { id: HexBoardSpaceId<Id> },
322
+ > = Omit<
323
+ BoardHexGridPropsGeneric<HexBoardTopology<Id>, TSpaceView>,
324
+ "board" | "interactions"
325
+ > & {
326
+ board: Id;
327
+ interactions?:
328
+ | "auto"
329
+ | false
330
+ | {
331
+ edge?: readonly InteractionKey[];
332
+ vertex?: readonly InteractionKey[];
333
+ space?: readonly InteractionKey[];
334
+ };
335
+ };
336
+
337
+ type WorkspaceBoard = Omit<DreamboardUI<typeof uiContract>["Board"], "HexView" | "HexGrid"> & {
338
+ HexView<
339
+ const Id extends HexBoardId,
340
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
341
+ >(props: HexBoardViewProps<Id, TSpaceView>): ReactElement;
342
+ HexGrid<
343
+ const Id extends HexBoardId,
344
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
345
+ >(props: HexBoardGridProps<Id, TSpaceView>): ReactElement;
346
+ };
347
+
348
+ type WorkspaceCardProperties = CardProperties extends Record<string, unknown>
349
+ ? CardProperties
350
+ : Record<string, unknown>;
351
+ type WorkspaceZoneId = [ZoneId] extends [never] ? string : ZoneId;
352
+ type WorkspaceCardId = [CardId] extends [never] ? string : CardId;
353
+ type WorkspaceCardType = [CardType] extends [never] ? string : CardType;
354
+
355
+ // ZoneCardRenderItem is a discriminated union (hidden: true | false).
356
+ // A bare Omit<..., "zone"> would collapse the union to its common keys and
357
+ // strip cardType / properties from the hydrated branch, defeating the
358
+ // discriminated-union narrowing the SDK contract promises. Distribute Omit
359
+ // across the union so each branch keeps its own shape.
360
+ type WorkspaceZoneCard = ZoneCardRenderItem<
361
+ WorkspaceCardId & string,
362
+ WorkspaceCardType & string,
363
+ WorkspaceCardProperties
364
+ > extends infer Item
365
+ ? Item extends { zone: string }
366
+ ? Omit<Item, "zone"> & { zone: WorkspaceZoneId }
367
+ : never
368
+ : never;
369
+
370
+ type WorkspaceZone = Omit<
371
+ DreamboardUI<typeof uiContract>["Zone"],
372
+ "CardAt" | "TopCard" | "List" | "PileCards"
373
+ > & {
374
+ CardAt(
375
+ props: Omit<ZoneCardAtProps<WorkspaceZoneId>, "zone" | "children"> & {
376
+ zone?: WorkspaceZoneId;
377
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
378
+ },
379
+ ): ReactElement | null;
380
+ TopCard(
381
+ props: Omit<
382
+ ZoneCardAtProps<WorkspaceZoneId>,
383
+ "zone" | "index" | "children"
384
+ > & {
385
+ zone?: WorkspaceZoneId;
386
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
387
+ },
388
+ ): ReactElement | null;
389
+ List(
390
+ props: Omit<ZoneListProps, "children"> & {
391
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
392
+ },
393
+ ): ReactElement;
394
+ PileCards(
395
+ props: Omit<ZonePileCardsProps, "renderCard"> & {
396
+ renderCard: (card: WorkspaceZoneCard) => ReactNode;
397
+ },
398
+ ): ReactElement | null;
399
+ };
400
+
401
+ type WorkspaceUI = Omit<
402
+ DreamboardUI<typeof uiContract>,
403
+ "Root" | "Game" | "Interaction" | "Board" | "Zone"
404
+ > & {
405
+ Root(props: UIRootProps): ReactElement;
406
+ readonly Game: TypedGame<typeof uiContract, GameView, PlayerId, PhaseName>;
407
+ readonly Interaction: Omit<
408
+ DreamboardUI<typeof uiContract>["Interaction"],
409
+ "Switch"
410
+ > & {
411
+ Switch(props: InteractionSwitchProps): ReactElement;
412
+ };
413
+ readonly Board: WorkspaceBoard;
414
+ readonly Zone: WorkspaceZone;
415
+ readonly ResourceCounter: ResourceCounterComponents<ResourceId>;
416
+ };
417
+
418
+ const baseUI = createDreamboardUI(uiContract);
419
+
420
+ const resourcePresentationById = literals.resourcePresentationById as Partial<
421
+ Record<string, { label?: string; icon?: string }>
422
+ >;
423
+
424
+ const resourceDisplayConfig = literals.resourceIds.map((resource) => {
425
+ const resourceId = resource as ResourceId;
426
+ const presentation = resourcePresentationById[resource as string];
427
+ return {
428
+ type: resourceId,
429
+ label: presentation?.label ?? resource,
430
+ icon: presentation?.icon ?? resource,
431
+ };
432
+ }) satisfies readonly ResourceDisplayConfig<ResourceId>[];
433
+
434
+ const resourceCounter = createResourceCounter<ResourceId>(resourceDisplayConfig);
435
+
436
+ export const Board: WorkspaceUI["Board"] = {
437
+ ...baseUI.Board,
438
+ HexView<
439
+ const Id extends HexBoardId,
440
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
441
+ >({ board: boardId, ...props }: HexBoardViewProps<Id, TSpaceView>) {
442
+ return createElement(baseUI.Board.HexView<HexBoardTopology<Id>, TSpaceView>, {
443
+ ...props,
444
+ board: hexStaticBoards[boardId],
445
+ });
446
+ },
447
+ HexGrid<
448
+ const Id extends HexBoardId,
449
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
450
+ >({ board: boardId, ...props }: HexBoardGridProps<Id, TSpaceView>) {
451
+ return createElement(baseUI.Board.HexGrid<HexBoardTopology<Id>, TSpaceView>, {
452
+ ...props,
453
+ board: hexStaticBoards[boardId],
454
+ });
455
+ },
456
+ } as WorkspaceUI["Board"];
457
+
458
+ export const UI: WorkspaceUI = {
459
+ ...baseUI,
460
+ Board,
461
+ ResourceCounter: resourceCounter,
462
+ };
463
+ export const Game: WorkspaceUI["Game"] = UI.Game;
464
+ export const Interaction = UI.Interaction;
465
+ export const Prompt = UI.Prompt;
466
+ export const PromptInbox = UI.PromptInbox;
467
+ export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
468
+ export const Dice: WorkspaceUI["Dice"] = UI.Dice;
469
+ export const Phase = UI.Phase;
470
+ export const Zone = UI.Zone;
471
+ export const ResourceCounter: WorkspaceUI["ResourceCounter"] = UI.ResourceCounter;
472
+
473
+ export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
474
+ game,
475
+ ) as ClientParamSchemaMap;
433
476
  `;
434
477
  }
435
478
 
436
479
  function generateReducerSupportSeed(): string {
437
480
  return `import {
438
- createReducerOps,
481
+ createReducerEdit,
439
482
  createStateQueries,
440
- pipe,
441
483
  } from "@dreamboard/app-sdk/reducer";
442
484
  import type { GameState } from "./game-contract";
443
485
 
@@ -456,16 +498,12 @@ import type { GameState } from "./game-contract";
456
498
  *
457
499
  * Recommended authoring pattern inside a phase reducer:
458
500
  *
459
- * const next = pipe(
460
- * state,
461
- * ops.setActivePlayers([q.players.order()[0]]),
462
- * );
463
- * return accept(next);
501
+ * const tx = edit(state);
502
+ * tx.setActivePlayers([q.players.order()[0]]);
503
+ * return accept(tx.state);
464
504
  */
465
505
 
466
- export const ops = createReducerOps<GameState>();
467
-
468
- export { pipe };
506
+ export const edit = createReducerEdit<GameState>();
469
507
 
470
508
  export function stateQueries(state: GameState) {
471
509
  return createStateQueries(state);
@@ -549,7 +587,7 @@ function generateReducerGameContractSeed(): string {
549
587
  import { ids, manifestContract } from "../shared/manifest-contract";
550
588
  import { defineGameContract, type GameStateOf } from "@dreamboard/app-sdk/reducer";
551
589
 
552
- // Contract files should stay focused on schemas, phase names, and exported
590
+ // Contract files should stay focused on schemas, phases, and exported
553
591
  // state types. Put reducers in app/phases/* and pure computations in
554
592
  // app/derived.ts or app/rules/*.
555
593
  const publicStateSchema = z.object({
@@ -561,15 +599,14 @@ const hiddenStateSchema = z.object({});
561
599
 
562
600
  export const gameContract = defineGameContract({
563
601
  manifest: manifestContract,
564
- // Keep this list in sync with the keys of the \`phases\` record in ./phases.
565
- // Declaring phase names here narrows \`fx.transition(...)\`, \`phase.dispatch\`,
566
- // and the flow state's \`currentPhase\` to a literal union.
567
- phaseNames: ["setup"] as const,
568
602
  state: {
569
603
  public: publicStateSchema,
570
604
  private: privateStateSchema,
571
605
  hidden: hiddenStateSchema,
572
606
  },
607
+ phases: {
608
+ setup: z.object({}),
609
+ },
573
610
  });
574
611
 
575
612
  export type GameContract = typeof gameContract;
@@ -683,8 +720,10 @@ function generateAppFrameworkTsConfig(): string {
683
720
  moduleResolution: "bundler",
684
721
  strict: true,
685
722
  esModuleInterop: true,
686
- skipLibCheck: true,
723
+ skipLibCheck: false,
724
+ types: [],
687
725
  declaration: false,
726
+ rootDir: "..",
688
727
  outDir: "./dist",
689
728
  paths: {
690
729
  "@dreamboard/manifest-contract": ["../shared/manifest-contract.ts"],
@@ -706,49 +745,57 @@ function generateAppFrameworkTsConfig(): string {
706
745
  }
707
746
 
708
747
  function generateUiFrameworkTsConfig(): string {
709
- return `{
710
- "compilerOptions": {
711
- "target": "ES2020",
712
- "module": "ESNext",
713
- "moduleResolution": "bundler",
714
- "jsx": "react-jsx",
715
- "strict": true,
716
- "esModuleInterop": true,
717
- "skipLibCheck": true,
718
- "paths": {
719
- "@dreamboard/manifest-contract": [
720
- "../shared/manifest-contract.ts"
721
- ],
722
- "@dreamboard/ui-contract": [
723
- "../shared/generated/ui-contract.ts"
724
- ],
725
- "@shared/*": [
726
- "../shared/*"
727
- ]
728
- }
729
- },
730
- "include": [
731
- "./**/*.ts",
732
- "./**/*.tsx",
733
- "../shared/**/*.ts"
734
- ],
735
- "exclude": [
736
- "node_modules",
737
- "dist"
738
- ]
739
- }
740
- `;
748
+ return `${JSON.stringify(
749
+ {
750
+ compilerOptions: {
751
+ target: "ES2020",
752
+ module: "ESNext",
753
+ moduleResolution: "bundler",
754
+ jsx: "react-jsx",
755
+ strict: true,
756
+ esModuleInterop: true,
757
+ skipLibCheck: false,
758
+ types: [],
759
+ paths: {
760
+ "@dreamboard/manifest-contract": ["../shared/manifest-contract.ts"],
761
+ "@dreamboard/ui-contract": ["../shared/generated/ui-contract.ts"],
762
+ "@shared/*": ["../shared/*"],
763
+ },
764
+ },
765
+ include: ["./**/*.ts", "./**/*.tsx", "../shared/**/*.ts"],
766
+ exclude: ["node_modules", "dist"],
767
+ },
768
+ null,
769
+ 2,
770
+ )}\n`;
741
771
  }
742
772
 
743
773
  function generateReducerUiAppContent(): string {
744
- return `import { Phase, PromptInbox } from "@dreamboard/ui-contract";
774
+ return `import { Game, Phase, Prompt, PromptInbox } from "@dreamboard/ui-contract";
745
775
 
746
776
  function SetupPhase() {
747
777
  return (
748
778
  <main>
749
779
  <PromptInbox.Root>
750
780
  <PromptInbox.Empty>No available prompts.</PromptInbox.Empty>
751
- <PromptInbox.Items />
781
+ <PromptInbox.Items>
782
+ {(prompt) => (
783
+ <Prompt.Root
784
+ key={prompt.interactionKey}
785
+ interaction={prompt.interactionKey}
786
+ >
787
+ <Prompt.Title />
788
+ <Prompt.Message />
789
+ <Prompt.Options>
790
+ {(option) => (
791
+ <Prompt.Option value={option.id}>
792
+ {option.label ?? option.id}
793
+ </Prompt.Option>
794
+ )}
795
+ </Prompt.Options>
796
+ </Prompt.Root>
797
+ )}
798
+ </PromptInbox.Items>
752
799
  </PromptInbox.Root>
753
800
  </main>
754
801
  );
@@ -756,11 +803,15 @@ function SetupPhase() {
756
803
 
757
804
  export default function App() {
758
805
  return (
759
- <Phase.Switch
760
- routes={{
761
- setup: () => <SetupPhase />,
762
- }}
763
- />
806
+ <Game.Root>
807
+ {() => (
808
+ <Phase.Switch
809
+ routes={{
810
+ setup: () => <SetupPhase />,
811
+ }}
812
+ />
813
+ )}
814
+ </Game.Root>
764
815
  );
765
816
  }
766
817
  `;