dreamboard 0.1.15 → 0.1.16

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.
@@ -5509,7 +5509,7 @@ function renderCardPropertiesSchemaByCardSetId(cardSets) {
5509
5509
  ` ${quote(cardSet.id)}: ${toPascalCase(cardSet.id)}CardPropertiesSchema,`
5510
5510
  ];
5511
5511
  });
5512
- return `const cardPropertiesSchemaByCardSetId: Record<string, z.ZodTypeAny> = {
5512
+ return `const cardPropertiesSchemaByCardSetId: Record<string, z.ZodType<unknown>> = {
5513
5513
  ${entries.join("\n")}
5514
5514
  };`;
5515
5515
  }
@@ -5715,7 +5715,7 @@ function renderCardStateSchemaById(analysis) {
5715
5715
  return `z.object(
5716
5716
  Object.fromEntries(
5717
5717
  literals.cardIds.map((cardId) => [cardId, createCardStateSchema(cardId)]),
5718
- ) as Record<CardId, z.ZodTypeAny>,
5718
+ ) as Record<CardId, z.ZodType<unknown>>,
5719
5719
  )`;
5720
5720
  }
5721
5721
  function renderCardStateSchemaFactory(analysis) {
@@ -5793,8 +5793,17 @@ function renderGenericBoardStateSchema(board, runtimeBoardId) {
5793
5793
  playerId: ${playerId ? `z.literal(${quote(playerId)})` : "ids.playerId.nullable().optional()"},
5794
5794
  templateId: z.string().nullable().optional(),
5795
5795
  fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
5796
+ // T220: per-board state.spaces is loose-keyed by string. The
5797
+ // canonical narrow id (\`ids.spaceId\`) is a Zod enum of EVERY
5798
+ // space id across every board, which makes a strict
5799
+ // \`z.record(enum, \u2026)\` reject any board whose spaces are a
5800
+ // proper subset of that enum. Loose keying matches the JSON wire
5801
+ // shape (\`additionalProperties\`); the inner \`id: ids.spaceId\`
5802
+ // narrows the value's spaceId at parse time. A future per-board
5803
+ // branded-id refactor (option B) can re-tighten this without a
5804
+ // wire change.
5796
5805
  spaces: z.record(
5797
- ids.spaceId,
5806
+ z.string(),
5798
5807
  z.object({
5799
5808
  id: ids.spaceId,
5800
5809
  name: z.string().nullable().optional(),
@@ -5840,8 +5849,9 @@ function renderHexBoardStateSchema(board, runtimeBoardId) {
5840
5849
  playerId: ${playerId ? `z.literal(${quote(playerId)})` : "ids.playerId.nullable().optional()"},
5841
5850
  templateId: z.string().nullable().optional(),
5842
5851
  fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
5852
+ // T220: see generic-board comment on the loose-keying choice.
5843
5853
  spaces: z.record(
5844
- ids.spaceId,
5854
+ z.string(),
5845
5855
  z.object({
5846
5856
  id: ids.spaceId,
5847
5857
  name: z.string().nullable().optional(),
@@ -5897,8 +5907,9 @@ function renderSquareBoardStateSchema(board, runtimeBoardId) {
5897
5907
  playerId: ${playerId ? `z.literal(${quote(playerId)})` : "ids.playerId.nullable().optional()"},
5898
5908
  templateId: z.string().nullable().optional(),
5899
5909
  fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
5910
+ // T220: see generic-board comment on the loose-keying choice.
5900
5911
  spaces: z.record(
5901
- ids.spaceId,
5912
+ z.string(),
5902
5913
  z.object({
5903
5914
  id: ids.spaceId,
5904
5915
  name: z.string().nullable().optional(),
@@ -7611,7 +7622,11 @@ const runtimeGenericBoardStateSchema = z.object({
7611
7622
  playerId: ids.playerId.nullable().optional(),
7612
7623
  templateId: z.string().nullable().optional(),
7613
7624
  fields: unknownRecordSchema,
7614
- spaces: z.record(ids.spaceId, boardSpaceStateSchema),
7625
+ // T220: per-board state.spaces is loose-keyed by string. See the
7626
+ // codegen-template comment in renderGenericBoardStateSchema for
7627
+ // the rationale; the wire shape is unchanged (additionalProperties
7628
+ // JSON), and the inner id field narrows at parse time.
7629
+ spaces: z.record(z.string(), boardSpaceStateSchema),
7615
7630
  relations: z.array(boardRelationStateSchema),
7616
7631
  containers: z.record(ids.boardContainerId, boardContainerStateSchema),
7617
7632
  });
@@ -7641,7 +7656,8 @@ const squareVertexStateSchema = z.object({
7641
7656
  });
7642
7657
  const runtimeHexBoardStateSchema = runtimeGenericBoardStateSchema.extend({
7643
7658
  layout: z.literal("hex"),
7644
- spaces: z.record(ids.spaceId, hexSpaceStateSchema),
7659
+ // T220: loose-keyed by string \u2014 see comment above.
7660
+ spaces: z.record(z.string(), hexSpaceStateSchema),
7645
7661
  relations: z.array(boardRelationStateSchema),
7646
7662
  containers: z.object({}),
7647
7663
  orientation: z.enum(["pointy-top", "flat-top"]),
@@ -7650,7 +7666,8 @@ const runtimeHexBoardStateSchema = runtimeGenericBoardStateSchema.extend({
7650
7666
  });
7651
7667
  const runtimeSquareBoardStateSchema = runtimeGenericBoardStateSchema.extend({
7652
7668
  layout: z.literal("square"),
7653
- spaces: z.record(ids.spaceId, squareSpaceStateSchema),
7669
+ // T220: loose-keyed by string \u2014 see comment above.
7670
+ spaces: z.record(z.string(), squareSpaceStateSchema),
7654
7671
  relations: z.array(boardRelationStateSchema),
7655
7672
  containers: z.record(ids.boardContainerId, boardContainerStateSchema),
7656
7673
  edges: z.array(hexEdgeStateSchema),
@@ -7862,11 +7879,11 @@ export const schemas = {
7862
7879
  } as const;
7863
7880
 
7864
7881
  export function createGameStateSchema<
7865
- PhaseNameSchema extends z.ZodTypeAny,
7866
- PublicSchema extends z.ZodTypeAny,
7867
- PrivateSchema extends z.ZodTypeAny,
7868
- HiddenSchema extends z.ZodTypeAny,
7869
- PhasesSchema extends z.ZodTypeAny,
7882
+ PhaseNameSchema extends z.ZodType<unknown>,
7883
+ PublicSchema extends z.ZodType<unknown>,
7884
+ PrivateSchema extends z.ZodType<unknown>,
7885
+ HiddenSchema extends z.ZodType<unknown>,
7886
+ PhasesSchema extends z.ZodType<unknown>,
7870
7887
  >({
7871
7888
  phaseNameSchema,
7872
7889
  publicSchema,
@@ -8050,6 +8067,27 @@ import { literals } from "./manifest-literals";
8050
8067
  import type { PlayerId as PublicPlayerId, TableState as PublicTableState } from "./manifest-types";
8051
8068
 
8052
8069
  const unknownRecordSchema`
8070
+ ).replaceAll(
8071
+ "literals.playerIds as unknown as readonly PlayerId[]",
8072
+ "literals.playerIds"
8073
+ ).replaceAll(
8074
+ "cardId: cardIdSchema as unknown as z.ZodType<CardId>,",
8075
+ "cardId: assumeManifestSchema<CardId>(cardIdSchema),"
8076
+ ).replaceAll(
8077
+ "deckId: deckIdSchema as unknown as z.ZodType<DeckId>,",
8078
+ "deckId: assumeManifestSchema<DeckId>(deckIdSchema),"
8079
+ ).replaceAll(
8080
+ "handId: handIdSchema as unknown as z.ZodType<HandId>,",
8081
+ "handId: assumeManifestSchema<HandId>(handIdSchema),"
8082
+ ).replace(
8083
+ /export const staticBoards = ([\s\S]*?) as const;\n\nconst baseInitialTable = /,
8084
+ "export const staticBoards = $1 as const satisfies StaticBoards<PublicTableState>;\n\nconst baseInitialTable = "
8085
+ ).replace(
8086
+ /const baseInitialTable = ([\s\S]*?) as const as unknown as TableState;\nconst baseDeckCardsByZoneId:/,
8087
+ "const baseInitialTable = cloneManifestDefault<PublicTableState>($1);\nconst baseDeckCardsByZoneId:"
8088
+ ).replace(
8089
+ "staticBoards: staticBoards as unknown as StaticBoards<TableState>,",
8090
+ "staticBoards,"
8053
8091
  ).replace(
8054
8092
  `export const manifestContract: ReducerManifestContract<
8055
8093
  RuntimeTableRecord,`,
@@ -8071,12 +8109,14 @@ const unknownRecordSchema`
8071
8109
  .min(1)
8072
8110
  .transform((value) => asPlayerId(value)),
8073
8111
  );`,
8074
- `const playerIdSchema = markManifestScopedSchema(
8075
- z
8076
- .string()
8077
- .min(1)
8078
- .transform((value) => asPlayerId(value)),
8079
- ) as unknown as z.ZodType<PublicPlayerId>;`
8112
+ `const playerIdSchema = assumeManifestSchema<PublicPlayerId>(
8113
+ markManifestScopedSchema(
8114
+ z
8115
+ .string()
8116
+ .min(1)
8117
+ .transform((value) => asPlayerId(value)),
8118
+ ),
8119
+ );`
8080
8120
  );
8081
8121
  }
8082
8122
  function renderCardSetTypeSections(analysis) {
@@ -9157,9 +9197,9 @@ import {
9157
9197
  createClientParamSchemasByPhase,
9158
9198
  } from "@dreamboard/app-sdk/reducer";
9159
9199
  import type {
9160
- ClientParamsOfInteractionOfDefinition,
9161
- DefaultedClientParamKeysOfInteractionOfDefinition,
9162
- InteractionIdOfDefinition,
9200
+ ClientParamsOfInteractionOfDefinition,
9201
+ DefaultedClientParamKeysOfInteractionOfDefinition,
9202
+ InteractionIdOfDefinition,
9163
9203
  InteractionIdOfDefinitionPhase,
9164
9204
  PhaseNamesOfDefinition,
9165
9205
  StageNamesOfDefinitionPhase,
@@ -9167,35 +9207,38 @@ import type {
9167
9207
  ViewOfDefinition,
9168
9208
  } from "@dreamboard/app-sdk/reducer";
9169
9209
  import {
9170
- createDreamboardUI,
9171
- createHexBoardView,
9172
- InteractionField as InteractionFieldGeneric,
9173
- InteractionForm as InteractionFormGeneric,
9174
- useActivePlayers as useActivePlayersGeneric,
9175
- useBoardInteractions as useBoardInteractionsGeneric,
9176
- useGameView as useGameViewGeneric,
9177
- useInteractionByKey as useInteractionByKeyGeneric,
9178
- usePlayerTurnOrder as usePlayerTurnOrderGeneric,
9179
- type BoardInteractionsContext,
9180
- type BoardInteractionsOptions,
9181
- type BoardSpaceIdOf,
9182
- type ClientParamSchemaMap,
9183
- type HexBoardView,
9184
- type InteractionDescriptor,
9185
- type InteractionFieldProps as InteractionFieldPropsGeneric,
9186
- type InteractionFieldRenderMap,
9187
- type InteractionFormProps as InteractionFormPropsGeneric,
9188
- type InteractionHandle,
9189
- type DreamboardUI,
9190
- type UIContract,
9191
- } from "@dreamboard/ui-sdk";
9192
- import { createElement, useMemo, type ReactElement } from "react";
9193
- import {
9194
- literals,
9195
- staticBoards,
9196
- type CardId,
9197
- type EdgeId,
9210
+ createDreamboardUI,
9211
+ createResourceCounter,
9212
+ type BoardHexGridProps as BoardHexGridPropsGeneric,
9213
+ type BoardHexViewProps as BoardHexViewPropsGeneric,
9214
+ type BoardSpaceIdOf,
9215
+ type ClientParamSchemaMap,
9216
+ type InteractionDescriptor,
9217
+ type InteractionHandle,
9218
+ type InteractionRouteMap as InteractionRouteMapGeneric,
9219
+ type InteractionSwitchProps as InteractionSwitchPropsGeneric,
9220
+ type InteractionSwitchRenderState,
9221
+ type DreamboardUI,
9222
+ type ResourceCounterComponents,
9223
+ type ResourceDisplayConfig,
9224
+ type TypedGame,
9225
+ type UIContract,
9226
+ type UIRootProps,
9227
+ type ZoneCardAtProps,
9228
+ type ZoneCardRenderItem,
9229
+ type ZoneListProps,
9230
+ type ZonePileCardsProps,
9231
+ } from "@dreamboard/ui-sdk";
9232
+ import { createElement, type ReactElement, type ReactNode } from "react";
9233
+ import {
9234
+ literals,
9235
+ staticBoards,
9236
+ type CardId,
9237
+ type CardProperties,
9238
+ type CardType,
9239
+ type EdgeId,
9198
9240
  type PlayerId,
9241
+ type ResourceId,
9199
9242
  type SpaceId,
9200
9243
  type VertexId,
9201
9244
  type ZoneId as ManifestZoneId,
@@ -9294,8 +9337,7 @@ export type InteractionDescriptorFor<Key extends InteractionKey> =
9294
9337
 
9295
9338
  /**
9296
9339
  * Params shape for a phase-qualified interaction key. Drives strong typing
9297
- * for \`useInteractionByKey\`'s draft,
9298
- * \`submit\`, and \`setInput\`.
9340
+ * for component-first interaction state, forms, and submits.
9299
9341
  */
9300
9342
  export type InteractionParamsOf<Key extends InteractionKey> =
9301
9343
  InteractionParams<PhaseOfInteractionKey<Key>, IdOfInteractionKey<Key>>;
@@ -9318,6 +9360,50 @@ type InteractionHandleDefaultedKeys<Key extends InteractionKey> = Extract<
9318
9360
  keyof InteractionParamsShape<Key> & string
9319
9361
  >;
9320
9362
 
9363
+ type RequiredInteractionInputKeysOf<Key extends InteractionKey> = Exclude<
9364
+ InteractionInputKeysOf<Key>,
9365
+ InteractionHandleDefaultedKeys<Key>
9366
+ >;
9367
+
9368
+ export type RequiredInteractionInputKey<Key extends InteractionKey> =
9369
+ RequiredInteractionInputKeysOf<Key>;
9370
+
9371
+ export type ZeroInputInteractionKey = {
9372
+ [K in InteractionKey]: InteractionInputKeysOf<K> extends never
9373
+ ? K
9374
+ : never;
9375
+ }[InteractionKey];
9376
+
9377
+ export type InputInteractionKey = Exclude<
9378
+ InteractionKey,
9379
+ ZeroInputInteractionKey
9380
+ >;
9381
+
9382
+ export type InteractionRouteRenderState<Key extends InteractionKey> =
9383
+ Omit<InteractionSwitchRenderState<Key>, "descriptor"> & {
9384
+ descriptor: InteractionDescriptorFor<Key>;
9385
+ };
9386
+
9387
+ export type InteractionRouteMap = {
9388
+ [K in InteractionKey]: (state: InteractionRouteRenderState<K>) => ReactNode;
9389
+ };
9390
+
9391
+ type ExactInteractionRoutes<Routes extends InteractionRouteMap> = Routes &
9392
+ Record<Exclude<keyof Routes, keyof InteractionRouteMap>, never>;
9393
+
9394
+ export function defineInteractionRoutes<const Routes extends InteractionRouteMap>(
9395
+ routes: ExactInteractionRoutes<Routes>,
9396
+ ): InteractionRouteMap {
9397
+ return routes;
9398
+ }
9399
+
9400
+ type InteractionSwitchProps = Omit<
9401
+ InteractionSwitchPropsGeneric<InteractionKey>,
9402
+ "routes"
9403
+ > & {
9404
+ routes: InteractionRouteMap;
9405
+ };
9406
+
9321
9407
  type InteractionInputKeyOf<Key extends InteractionKey> =
9322
9408
  Key extends InteractionKey ? InteractionInputKeysOf<Key> : never;
9323
9409
 
@@ -9349,6 +9435,10 @@ type UIPromptOptionRegistry = {
9349
9435
  [K in PromptOptionValue]: { value: K };
9350
9436
  };
9351
9437
 
9438
+ type UIPlayerRegistry = {
9439
+ [K in PlayerId & string]: { player: K };
9440
+ };
9441
+
9352
9442
  type UIZoneRegistry = {
9353
9443
  [K in ZoneId & string]: { zone: K };
9354
9444
  };
@@ -9370,6 +9460,7 @@ export const uiContract = {
9370
9460
  inputs: {} as UIInputRegistry,
9371
9461
  prompts: {} as UIPromptRegistry,
9372
9462
  promptOptions: {} as UIPromptOptionRegistry,
9463
+ players: {} as UIPlayerRegistry,
9373
9464
  zones: {} as UIZoneRegistry,
9374
9465
  cards: {} as UICardRegistry,
9375
9466
  phases: {} as UIPhaseRegistry,
@@ -9383,142 +9474,6 @@ declare module "@dreamboard/ui-sdk" {
9383
9474
  }
9384
9475
  }
9385
9476
 
9386
- type WorkspaceUI = DreamboardUI<typeof uiContract>;
9387
-
9388
- export const UI: WorkspaceUI = createDreamboardUI(uiContract);
9389
- export const Interaction = UI.Interaction;
9390
- export const Prompt = UI.Prompt;
9391
- export const PromptInbox = UI.PromptInbox;
9392
- export const PromptDialogHost = UI.PromptDialogHost;
9393
- export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
9394
- export const Phase = UI.Phase;
9395
- export const Zone = UI.Zone;
9396
- export const Board = UI.Board;
9397
-
9398
- export type InteractionFieldRenderers<Key extends InteractionKey> =
9399
- InteractionFieldRenderMap<InteractionParamsShape<Key>>;
9400
-
9401
- export type InteractionFormProps<Key extends InteractionKey> = Omit<
9402
- InteractionFormPropsGeneric<
9403
- InteractionParamsShape<Key>,
9404
- InteractionHandleDefaultedKeys<Key>
9405
- >,
9406
- "descriptor" | "handle"
9407
- > & {
9408
- descriptor: InteractionDescriptorFor<Key>;
9409
- handle: InteractionHandle<
9410
- InteractionParamsShape<Key>,
9411
- InteractionHandleDefaultedKeys<Key>
9412
- >;
9413
- renderFields?: InteractionFieldRenderers<Key>;
9414
- };
9415
-
9416
- export type InteractionFieldProps<
9417
- Key extends InteractionKey,
9418
- InputKey extends keyof InteractionParamsShape<Key> & string,
9419
- > = Omit<
9420
- InteractionFieldPropsGeneric<InteractionParamsShape<Key>, InputKey>,
9421
- "descriptor" | "handle"
9422
- > & {
9423
- descriptor: InteractionDescriptorFor<Key>;
9424
- handle: InteractionHandle<
9425
- InteractionParamsShape<Key>,
9426
- InteractionHandleDefaultedKeys<Key>
9427
- >;
9428
- };
9429
-
9430
- export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
9431
- game,
9432
- ) as ClientParamSchemaMap;
9433
-
9434
- /**
9435
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s generic
9436
- * \`useInteractionByKey\`. The \`key\` argument is constrained to the
9437
- * generated {@link InteractionKey} union (typos are compile errors), and
9438
- * the returned {@link InteractionHandle} is parameterised on
9439
- * {@link InteractionParamsOf} so \`handle.draft\`, \`handle.submit\`, and
9440
- * \`handle.setInput\` are statically typed against the interaction's
9441
- * declared inputs.
9442
- *
9443
- * \`\`\`ts
9444
- * const handle = useInteractionByKey("play.placeThingCard");
9445
- * if (!handle) return null;
9446
- * handle.setInput("cardId", card.id); // inferred as ThingsDeckCardId
9447
- * await handle.submit(); // params typed from the reducer contract
9448
- * \`\`\`
9449
- */
9450
- export function useInteractionByKey<Key extends InteractionKey>(
9451
- key: Key | null | undefined,
9452
- ): InteractionHandle<
9453
- InteractionParamsShape<Key>,
9454
- InteractionHandleDefaultedKeys<Key>
9455
- > | null {
9456
- return useInteractionByKeyGeneric<
9457
- Key,
9458
- InteractionParamsShape<Key>,
9459
- InteractionHandleDefaultedKeys<Key>
9460
- >(key);
9461
- }
9462
-
9463
- export function InteractionForm<Key extends InteractionKey>(
9464
- props: InteractionFormProps<Key>,
9465
- ): ReactElement {
9466
- return createElement(
9467
- InteractionFormGeneric<
9468
- InteractionParamsShape<Key>,
9469
- InteractionHandleDefaultedKeys<Key>
9470
- >,
9471
- props,
9472
- );
9473
- }
9474
-
9475
- export function InteractionField<
9476
- Key extends InteractionKey,
9477
- InputKey extends keyof InteractionParamsShape<Key> & string,
9478
- >(props: InteractionFieldProps<Key, InputKey>): ReactElement | null {
9479
- return createElement(
9480
- InteractionFieldGeneric<InteractionParamsShape<Key>, InputKey>,
9481
- props,
9482
- );
9483
- }
9484
-
9485
- /**
9486
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s
9487
- * \`useBoardInteractions\`. Returns a {@link BoardInteractionsContext}
9488
- * narrowed to this workspace's board-interaction union for exhaustive
9489
- * downstream logic.
9490
- *
9491
- * \`\`\`tsx
9492
- * const board = useBoardInteractions();
9493
- * <HexGrid interactiveVertices={board.targetLayers.vertex()} />
9494
- * \`\`\`
9495
- *
9496
- * Reach for this whenever a screen keeps more than one board target
9497
- * interaction live simultaneously and dispatch is target-driven instead of
9498
- * armed-then-clicked. Ambiguous unarmed overlaps should be resolved in the UI
9499
- * by arming an explicit interaction/input route.
9500
- */
9501
- export function useBoardInteractions(
9502
- options?: BoardInteractionsOptions,
9503
- ): BoardInteractionsContext<BoardInteractions> {
9504
- return useBoardInteractionsGeneric<BoardInteractions>(options);
9505
- }
9506
-
9507
- /** Workspace-typed active-player hook. */
9508
- export function useActivePlayers(): readonly PlayerId[] {
9509
- return useActivePlayersGeneric() as readonly PlayerId[];
9510
- }
9511
-
9512
- /** Workspace-typed reducer-native player view hook. */
9513
- export function useGameView(): GameView {
9514
- return useGameViewGeneric() as GameView;
9515
- }
9516
-
9517
- /** Workspace-typed player turn order hook. */
9518
- export function usePlayerTurnOrder(): readonly PlayerId[] {
9519
- return usePlayerTurnOrderGeneric() as readonly PlayerId[];
9520
- }
9521
-
9522
9477
  // -------------------------------------------------------------------------
9523
9478
  // Typed hex-board view adapter
9524
9479
  // -------------------------------------------------------------------------
@@ -9537,49 +9492,176 @@ export type HexBoardSpaceId<Id extends HexBoardId> = BoardSpaceIdOf<
9537
9492
  HexBoardTopology<Id>
9538
9493
  >;
9539
9494
 
9540
- /**
9541
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s
9542
- * \`createHexBoardView\`. Joins the static topology of the named hex
9543
- * board with a per-space view overlay and returns a value ready for
9544
- * \`<HexGrid board={...} />\`. Each rendered tile carries a \`view\`
9545
- * field typed as the matched overlay row.
9546
- *
9547
- * Strict by construction: every static space must have exactly one
9548
- * overlay; missing, duplicate, or unknown overlay ids throw.
9549
- *
9550
- * \`\`\`tsx
9551
- * const island = useHexBoardView("island", { spaces: view.spaces });
9552
- *
9553
- * <HexGrid
9554
- * board={island}
9555
- * renderTile={(tile) => {
9556
- * const terrain = tile.view.terrain;
9557
- * // ...
9558
- * }}
9559
- * />
9560
- * \`\`\`
9561
- */
9562
- export function useHexBoardView<
9563
- const Id extends HexBoardId,
9564
- const TSpaceView extends { id: HexBoardSpaceId<Id> },
9565
- >(
9566
- boardId: Id,
9567
- options: { spaces: ReadonlyArray<TSpaceView> },
9568
- ): HexBoardView<HexBoardTopology<Id>, TSpaceView> {
9569
- const board = hexStaticBoards[boardId];
9570
- const { spaces } = options;
9571
- return useMemo(
9572
- () => createHexBoardView<HexBoardTopology<Id>, TSpaceView>(board, { spaces }),
9573
- [board, spaces],
9574
- );
9575
- }
9495
+ export type HexBoardViewProps<
9496
+ Id extends HexBoardId,
9497
+ TSpaceView extends { id: HexBoardSpaceId<Id> },
9498
+ > = Omit<BoardHexViewPropsGeneric<HexBoardTopology<Id>, TSpaceView>, "board"> & {
9499
+ board: Id;
9500
+ };
9501
+
9502
+ export type HexBoardGridProps<
9503
+ Id extends HexBoardId,
9504
+ TSpaceView extends { id: HexBoardSpaceId<Id> },
9505
+ > = Omit<
9506
+ BoardHexGridPropsGeneric<HexBoardTopology<Id>, TSpaceView>,
9507
+ "board" | "interactions"
9508
+ > & {
9509
+ board: Id;
9510
+ interactions?:
9511
+ | "auto"
9512
+ | false
9513
+ | {
9514
+ edge?: readonly InteractionKey[];
9515
+ vertex?: readonly InteractionKey[];
9516
+ space?: readonly InteractionKey[];
9517
+ };
9518
+ };
9519
+
9520
+ type WorkspaceBoard = Omit<DreamboardUI<typeof uiContract>["Board"], "HexView" | "HexGrid"> & {
9521
+ HexView<
9522
+ const Id extends HexBoardId,
9523
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
9524
+ >(props: HexBoardViewProps<Id, TSpaceView>): ReactElement;
9525
+ HexGrid<
9526
+ const Id extends HexBoardId,
9527
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
9528
+ >(props: HexBoardGridProps<Id, TSpaceView>): ReactElement;
9529
+ };
9530
+
9531
+ type WorkspaceCardProperties = CardProperties extends Record<string, unknown>
9532
+ ? CardProperties
9533
+ : Record<string, unknown>;
9534
+ type WorkspaceZoneId = [ZoneId] extends [never] ? string : ZoneId;
9535
+ type WorkspaceCardId = [CardId] extends [never] ? string : CardId;
9536
+ type WorkspaceCardType = [CardType] extends [never] ? string : CardType;
9537
+
9538
+ // ZoneCardRenderItem is a discriminated union (hidden: true | false).
9539
+ // A bare Omit<..., "zone"> would collapse the union to its common keys and
9540
+ // strip cardType / properties from the hydrated branch, defeating the
9541
+ // discriminated-union narrowing the SDK contract promises. Distribute Omit
9542
+ // across the union so each branch keeps its own shape.
9543
+ type WorkspaceZoneCard = ZoneCardRenderItem<
9544
+ WorkspaceCardId & string,
9545
+ WorkspaceCardType & string,
9546
+ WorkspaceCardProperties
9547
+ > extends infer Item
9548
+ ? Item extends { zone: string }
9549
+ ? Omit<Item, "zone"> & { zone: WorkspaceZoneId }
9550
+ : never
9551
+ : never;
9552
+
9553
+ type WorkspaceZone = Omit<
9554
+ DreamboardUI<typeof uiContract>["Zone"],
9555
+ "CardAt" | "TopCard" | "List" | "PileCards"
9556
+ > & {
9557
+ CardAt(
9558
+ props: Omit<ZoneCardAtProps<WorkspaceZoneId>, "zone" | "children"> & {
9559
+ zone?: WorkspaceZoneId;
9560
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9561
+ },
9562
+ ): ReactElement | null;
9563
+ TopCard(
9564
+ props: Omit<
9565
+ ZoneCardAtProps<WorkspaceZoneId>,
9566
+ "zone" | "index" | "children"
9567
+ > & {
9568
+ zone?: WorkspaceZoneId;
9569
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9570
+ },
9571
+ ): ReactElement | null;
9572
+ List(
9573
+ props: Omit<ZoneListProps, "children"> & {
9574
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9575
+ },
9576
+ ): ReactElement;
9577
+ PileCards(
9578
+ props: Omit<ZonePileCardsProps, "renderCard"> & {
9579
+ renderCard: (card: WorkspaceZoneCard) => ReactNode;
9580
+ },
9581
+ ): ReactElement | null;
9582
+ };
9583
+
9584
+ type WorkspaceUI = Omit<
9585
+ DreamboardUI<typeof uiContract>,
9586
+ "Root" | "Game" | "Interaction" | "Board" | "Zone"
9587
+ > & {
9588
+ Root(props: UIRootProps): ReactElement;
9589
+ readonly Game: TypedGame<typeof uiContract, GameView, PlayerId, PhaseName>;
9590
+ readonly Interaction: Omit<
9591
+ DreamboardUI<typeof uiContract>["Interaction"],
9592
+ "Switch"
9593
+ > & {
9594
+ Switch(props: InteractionSwitchProps): ReactElement;
9595
+ };
9596
+ readonly Board: WorkspaceBoard;
9597
+ readonly Zone: WorkspaceZone;
9598
+ readonly ResourceCounter: ResourceCounterComponents<ResourceId>;
9599
+ };
9600
+
9601
+ const baseUI = createDreamboardUI(uiContract);
9602
+
9603
+ const resourcePresentationById = literals.resourcePresentationById as Partial<
9604
+ Record<string, { label?: string; icon?: string }>
9605
+ >;
9606
+
9607
+ const resourceDisplayConfig = literals.resourceIds.map((resource) => {
9608
+ const resourceId = resource as ResourceId;
9609
+ const presentation = resourcePresentationById[resource as string];
9610
+ return {
9611
+ type: resourceId,
9612
+ label: presentation?.label ?? resource,
9613
+ icon: presentation?.icon ?? resource,
9614
+ };
9615
+ }) satisfies readonly ResourceDisplayConfig<ResourceId>[];
9616
+
9617
+ const resourceCounter = createResourceCounter<ResourceId>(resourceDisplayConfig);
9618
+
9619
+ export const Board: WorkspaceUI["Board"] = {
9620
+ ...baseUI.Board,
9621
+ HexView<
9622
+ const Id extends HexBoardId,
9623
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
9624
+ >({ board: boardId, ...props }: HexBoardViewProps<Id, TSpaceView>) {
9625
+ return createElement(baseUI.Board.HexView<HexBoardTopology<Id>, TSpaceView>, {
9626
+ ...props,
9627
+ board: hexStaticBoards[boardId],
9628
+ });
9629
+ },
9630
+ HexGrid<
9631
+ const Id extends HexBoardId,
9632
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
9633
+ >({ board: boardId, ...props }: HexBoardGridProps<Id, TSpaceView>) {
9634
+ return createElement(baseUI.Board.HexGrid<HexBoardTopology<Id>, TSpaceView>, {
9635
+ ...props,
9636
+ board: hexStaticBoards[boardId],
9637
+ });
9638
+ },
9639
+ } as WorkspaceUI["Board"];
9640
+
9641
+ export const UI: WorkspaceUI = {
9642
+ ...baseUI,
9643
+ Board,
9644
+ ResourceCounter: resourceCounter,
9645
+ };
9646
+ export const Game: WorkspaceUI["Game"] = UI.Game;
9647
+ export const Interaction = UI.Interaction;
9648
+ export const Prompt = UI.Prompt;
9649
+ export const PromptInbox = UI.PromptInbox;
9650
+ export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
9651
+ export const Dice: WorkspaceUI["Dice"] = UI.Dice;
9652
+ export const Phase = UI.Phase;
9653
+ export const Zone = UI.Zone;
9654
+ export const ResourceCounter: WorkspaceUI["ResourceCounter"] = UI.ResourceCounter;
9655
+
9656
+ export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
9657
+ game,
9658
+ ) as ClientParamSchemaMap;
9576
9659
  `;
9577
9660
  }
9578
9661
  function generateReducerSupportSeed() {
9579
9662
  return `import {
9580
- createReducerOps,
9663
+ createReducerEdit,
9581
9664
  createStateQueries,
9582
- pipe,
9583
9665
  } from "@dreamboard/app-sdk/reducer";
9584
9666
  import type { GameState } from "./game-contract";
9585
9667
 
@@ -9598,16 +9680,12 @@ import type { GameState } from "./game-contract";
9598
9680
  *
9599
9681
  * Recommended authoring pattern inside a phase reducer:
9600
9682
  *
9601
- * const next = pipe(
9602
- * state,
9603
- * ops.setActivePlayers([q.players.order()[0]]),
9604
- * );
9605
- * return accept(next);
9683
+ * const tx = edit(state);
9684
+ * tx.setActivePlayers([q.players.order()[0]]);
9685
+ * return accept(tx.state);
9606
9686
  */
9607
9687
 
9608
- export const ops = createReducerOps<GameState>();
9609
-
9610
- export { pipe };
9688
+ export const edit = createReducerEdit<GameState>();
9611
9689
 
9612
9690
  export function stateQueries(state: GameState) {
9613
9691
  return createStateQueries(state);
@@ -9688,7 +9766,7 @@ function generateReducerGameContractSeed() {
9688
9766
  import { ids, manifestContract } from "../shared/manifest-contract";
9689
9767
  import { defineGameContract, type GameStateOf } from "@dreamboard/app-sdk/reducer";
9690
9768
 
9691
- // Contract files should stay focused on schemas, phase names, and exported
9769
+ // Contract files should stay focused on schemas, phases, and exported
9692
9770
  // state types. Put reducers in app/phases/* and pure computations in
9693
9771
  // app/derived.ts or app/rules/*.
9694
9772
  const publicStateSchema = z.object({
@@ -9700,15 +9778,14 @@ const hiddenStateSchema = z.object({});
9700
9778
 
9701
9779
  export const gameContract = defineGameContract({
9702
9780
  manifest: manifestContract,
9703
- // Keep this list in sync with the keys of the \`phases\` record in ./phases.
9704
- // Declaring phase names here narrows \`fx.transition(...)\`, \`phase.dispatch\`,
9705
- // and the flow state's \`currentPhase\` to a literal union.
9706
- phaseNames: ["setup"] as const,
9707
9781
  state: {
9708
9782
  public: publicStateSchema,
9709
9783
  private: privateStateSchema,
9710
9784
  hidden: hiddenStateSchema,
9711
9785
  },
9786
+ phases: {
9787
+ setup: z.object({}),
9788
+ },
9712
9789
  });
9713
9790
 
9714
9791
  export type GameContract = typeof gameContract;
@@ -9809,8 +9886,10 @@ function generateAppFrameworkTsConfig() {
9809
9886
  moduleResolution: "bundler",
9810
9887
  strict: true,
9811
9888
  esModuleInterop: true,
9812
- skipLibCheck: true,
9889
+ skipLibCheck: false,
9890
+ types: [],
9813
9891
  declaration: false,
9892
+ rootDir: "..",
9814
9893
  outDir: "./dist",
9815
9894
  paths: {
9816
9895
  "@dreamboard/manifest-contract": ["../shared/manifest-contract.ts"],
@@ -9832,48 +9911,57 @@ function generateAppFrameworkTsConfig() {
9832
9911
  `;
9833
9912
  }
9834
9913
  function generateUiFrameworkTsConfig() {
9835
- return `{
9836
- "compilerOptions": {
9837
- "target": "ES2020",
9838
- "module": "ESNext",
9839
- "moduleResolution": "bundler",
9840
- "jsx": "react-jsx",
9841
- "strict": true,
9842
- "esModuleInterop": true,
9843
- "skipLibCheck": true,
9844
- "paths": {
9845
- "@dreamboard/manifest-contract": [
9846
- "../shared/manifest-contract.ts"
9847
- ],
9848
- "@dreamboard/ui-contract": [
9849
- "../shared/generated/ui-contract.ts"
9850
- ],
9851
- "@shared/*": [
9852
- "../shared/*"
9853
- ]
9854
- }
9855
- },
9856
- "include": [
9857
- "./**/*.ts",
9858
- "./**/*.tsx",
9859
- "../shared/**/*.ts"
9860
- ],
9861
- "exclude": [
9862
- "node_modules",
9863
- "dist"
9864
- ]
9865
- }
9914
+ return `${JSON.stringify(
9915
+ {
9916
+ compilerOptions: {
9917
+ target: "ES2020",
9918
+ module: "ESNext",
9919
+ moduleResolution: "bundler",
9920
+ jsx: "react-jsx",
9921
+ strict: true,
9922
+ esModuleInterop: true,
9923
+ skipLibCheck: false,
9924
+ types: [],
9925
+ paths: {
9926
+ "@dreamboard/manifest-contract": ["../shared/manifest-contract.ts"],
9927
+ "@dreamboard/ui-contract": ["../shared/generated/ui-contract.ts"],
9928
+ "@shared/*": ["../shared/*"]
9929
+ }
9930
+ },
9931
+ include: ["./**/*.ts", "./**/*.tsx", "../shared/**/*.ts"],
9932
+ exclude: ["node_modules", "dist"]
9933
+ },
9934
+ null,
9935
+ 2
9936
+ )}
9866
9937
  `;
9867
9938
  }
9868
9939
  function generateReducerUiAppContent() {
9869
- return `import { Phase, PromptInbox } from "@dreamboard/ui-contract";
9940
+ return `import { Game, Phase, Prompt, PromptInbox } from "@dreamboard/ui-contract";
9870
9941
 
9871
9942
  function SetupPhase() {
9872
9943
  return (
9873
9944
  <main>
9874
9945
  <PromptInbox.Root>
9875
9946
  <PromptInbox.Empty>No available prompts.</PromptInbox.Empty>
9876
- <PromptInbox.Items />
9947
+ <PromptInbox.Items>
9948
+ {(prompt) => (
9949
+ <Prompt.Root
9950
+ key={prompt.interactionKey}
9951
+ interaction={prompt.interactionKey}
9952
+ >
9953
+ <Prompt.Title />
9954
+ <Prompt.Message />
9955
+ <Prompt.Options>
9956
+ {(option) => (
9957
+ <Prompt.Option value={option.id}>
9958
+ {option.label ?? option.id}
9959
+ </Prompt.Option>
9960
+ )}
9961
+ </Prompt.Options>
9962
+ </Prompt.Root>
9963
+ )}
9964
+ </PromptInbox.Items>
9877
9965
  </PromptInbox.Root>
9878
9966
  </main>
9879
9967
  );
@@ -9881,11 +9969,15 @@ function SetupPhase() {
9881
9969
 
9882
9970
  export default function App() {
9883
9971
  return (
9884
- <Phase.Switch
9885
- routes={{
9886
- setup: () => <SetupPhase />,
9887
- }}
9888
- />
9972
+ <Game.Root>
9973
+ {() => (
9974
+ <Phase.Switch
9975
+ routes={{
9976
+ setup: () => <SetupPhase />,
9977
+ }}
9978
+ />
9979
+ )}
9980
+ </Game.Root>
9889
9981
  );
9890
9982
  }
9891
9983
  `;
@@ -21316,6 +21408,11 @@ var ShadowReducerRuntime = class {
21316
21408
  this.version = version;
21317
21409
  this.started = true;
21318
21410
  }
21411
+ patchState(mutator) {
21412
+ const next = structuredClone(this.rawState());
21413
+ mutator(next);
21414
+ this.hydrate(next, this.version + 1);
21415
+ }
21319
21416
  projectAllSeats() {
21320
21417
  const projection = this.runtime.projectSeatsDynamic({
21321
21418
  playerIds: this.playerIds
@@ -21616,6 +21713,15 @@ async function createScenarioContext(options) {
21616
21713
  await assertLiveMatchesShadow("browser", options.shadow, live);
21617
21714
  }
21618
21715
  },
21716
+ patchState: async (mutator) => {
21717
+ await api.start();
21718
+ if (options.live || options.embedded || options.browser || options.actionPlan) {
21719
+ throw new Error(
21720
+ "game.patchState is only supported for reducer snapshot scenarios. Use it to materialize a state before --from-scenario or reducer tests, not inside live replay/browser runners."
21721
+ );
21722
+ }
21723
+ options.shadow.patchState(mutator);
21724
+ },
21619
21725
  submit: async (playerId, interactionId, params) => {
21620
21726
  await api.start();
21621
21727
  const interactionParams = params ?? {};
@@ -26119,4 +26225,4 @@ export {
26119
26225
  test_default,
26120
26226
  runDreamboardCli
26121
26227
  };
26122
- //# sourceMappingURL=chunk-6FPQ3S2J.js.map
26228
+ //# sourceMappingURL=chunk-WDTE6OFH.js.map