dreamboard 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -71,24 +71,24 @@ Clone an existing game:
71
71
  dreamboard clone my-game
72
72
  ```
73
73
 
74
- Update local manifest/rule changes and regenerate scaffolded files:
74
+ Sync local authored changes and regenerate scaffolded files:
75
75
 
76
76
  ```bash
77
- dreamboard update
78
- dreamboard update --update-sdk
77
+ dreamboard sync
78
+ dreamboard sync --update-sdk
79
79
  ```
80
80
 
81
- If the remote has advanced unexpectedly, `dreamboard update` fails fast and keeps the local workspace as the source of truth. Reconcile explicitly with:
81
+ If the remote has advanced unexpectedly, reconcile authored changes explicitly with:
82
82
 
83
83
  ```bash
84
- dreamboard update --pull
84
+ dreamboard pull
85
85
  ```
86
86
 
87
- Push local edits (recompile):
87
+ Compile the current authored head:
88
88
 
89
89
  ```bash
90
- dreamboard push
91
- dreamboard push --force
90
+ dreamboard compile
91
+ dreamboard compile --skip-local-check
92
92
  ```
93
93
 
94
94
  Inspect local vs remote state:
@@ -107,7 +107,7 @@ dreamboard run --seed 1337
107
107
  dreamboard run --new-session
108
108
  ```
109
109
 
110
- If no successful compile exists yet, `dreamboard run` will compile from the latest scaffolded snapshot automatically.
110
+ If no successful compile exists for the current authored state yet, `dreamboard run` will ask you to run `dreamboard compile` first.
111
111
  By default, the CLI uses `manifest.json`'s `playerConfig.minPlayers` to decide how many seats to create.
112
112
 
113
113
  `dreamboard run` now defaults to a wait-and-observe loop when no scenario is provided:
@@ -1936,11 +1936,11 @@ var SKILL_ASSET_FILES = {
1936
1936
  "references/all-players-tracking.md": '# ALL_PLAYERS Phase - Tracking Who Has Acted\n\nThe engine automatically tracks action progress in `ALL_PLAYERS` (`MultiplePlayer`) phases through `ctx.phase`. Do not implement manual KV tracking for this.\n\n## Built-in phase tracking API\n\nUse `ctx.phase` in `validateAction` and `checkCompletion`:\n\n- `ctx.phase.hasPlayerActed(playerId)` - has this player already submitted an action?\n- `ctx.phase.haveAllExpectedPlayersActed()` - are all currently expected players done?\n- `ctx.phase.getPlayersStillWaiting()` - which players are still pending?\n- `ctx.phase.getExpectedPlayers()` - who is expected to act this phase?\n- `ctx.phase.getPlayersWhoActed()` - who has already acted?\n\n## Recommended pattern\n\n```typescript\nimport type { MultiplePlayerPhaseDefinition } from "../sdk/phaseHandlers";\nimport { createMultiplePlayerPhase } from "../sdk/phaseHandlers";\nimport { validationSuccess, validationError } from "../sdk/validation.js";\n\ntype PhaseAction = "revealCard";\n\nconst phaseDefinition = {\n onEnter(ctx) {\n const { state, apis } = ctx;\n apis.gameApi.setActivePlayers(state.player.getOrder());\n // No manual tracking setup required.\n },\n\n validateAction(ctx, playerId, actionType, parameters) {\n if (ctx.phase.hasPlayerActed(playerId)) {\n return validationError("already-acted", "You already acted this phase");\n }\n return validationSuccess();\n },\n\n onPlayerAction(ctx, playerId, actionType, parameters) {\n const { apis } = ctx;\n // ... process action ...\n // Engine tracking updates automatically after accepted actions.\n },\n\n checkCompletion(ctx) {\n if (ctx.phase.haveAllExpectedPlayersActed()) {\n return "nextPhase";\n }\n return null;\n },\n\n onComplete(ctx) {\n // Optional cleanup only. No action-tracking cleanup required.\n },\n\n getUIArgs(ctx, playerId) {\n return {\n waitingOn: ctx.phase.getPlayersStillWaiting(),\n };\n },\n} satisfies MultiplePlayerPhaseDefinition<"revealCards", PhaseAction>;\n\nexport const phase = createMultiplePlayerPhase(phaseDefinition);\n```\n\n## If UI needs progress data\n\n`ctx.phase` is available in app logic. For UI, pass derived values via `getUIArgs()` (for example `waitingOn`), or write explicit UI-facing values via global/player state APIs.\n\n## Checklist for ALL_PLAYERS phases\n\n1. Call `apis.gameApi.setActivePlayers(state.player.getOrder())` in `onEnter`.\n2. Block duplicate actions with `ctx.phase.hasPlayerActed(playerId)` in `validateAction`.\n3. Process each action in `onPlayerAction` without manual tracking writes.\n4. Transition from `checkCompletion` when `ctx.phase.haveAllExpectedPlayersActed()` is true.\n5. Use `ctx.phase.getPlayersStillWaiting()` / `getPlayersWhoActed()` for logs or UI args as needed.\n',
1937
1937
  "references/api-reference.md": '# State & API Reference\n\nUse this page as a lookup while writing `app/phases/*.ts`.\n\nThis is a reference-first document, not a modeling guide. For schema design, use [manifest-authoring.md](manifest-authoring.md). For hand-vs-deck decisions, use [hands-vs-decks.md](hands-vs-decks.md).\n\n## Read APIs (`ctx.state.*`)\n\n### `state.player`\n\n| Method | Returns | Description |\n| -------------------------------------- | ------------- | ------------------------------------- |\n| `.getOrder()` | `PlayerId[]` | All players in turn order |\n| `.getCurrentIds()` | `PlayerId[]` | Currently active player IDs |\n| `.get(playerId)` | `Player` | Player data: `{ id, name, score }` |\n| `.getState(playerId)` | `PlayerState` | Player variables from manifest schema |\n| `.getHand(playerId, handId)` | `CardId[]` | Cards in a specific hand |\n| `.getAllHands(playerId)` | `CardId[]` | All cards across all hands |\n| `.isInHand(playerId, cardId, handId?)` | `boolean` | Check if card is in hand |\n| `.isActive(playerId)` | `boolean` | Is player currently active |\n\n### `state.deck`\n\n| Method | Returns | Description |\n| --------------------- | ---------------- | ---------------------- |\n| `.getCards(deckId)` | `CardId[]` | Cards in a shared deck |\n| `.getTopCard(deckId)` | `CardId \\| null` | Top card of deck |\n\n### `state.card`\n\n| Method | Returns | Description |\n| -------------------------------- | ------------------ | ---------------------------------------------------- |\n| `.get(cardId)` | `Card` | Full card data |\n| `.getProperties(cardId)` | Typed properties | Card-specific props (rank, suit, etc.) |\n| `.getLocation(cardId)` | `Location` | Where the card is (InHand, InDeck, InZone, Detached) |\n| `.getPlayedBy(cardId)` | `PlayerId \\| null` | Who played this card to a deck/zone |\n| `.getOwner(cardId)` | `PlayerId \\| null` | Card owner |\n| `.isVisibleTo(cardId, playerId)` | `boolean` | Visibility check |\n| `.getOwnedBy(playerId)` | `CardId[]` | All cards owned by player |\n\n### `state.game`\n\n| Method | Returns | Description |\n| -------------------- | ------------- | ------------------------------------- |\n| `.getGlobalState()` | `GlobalState` | Global variables from manifest schema |\n| `.getCurrentState()` | `StateName` | Current state machine state |\n\n## Mutation APIs (`ctx.apis.*`)\n\n### `apis.cardApi`\n\n| Method | Description |\n| --------------------------------------------------------------------------- | ------------------------------ |\n| `.moveCardsFromHandToDeck(playerId, handId, cardIds, deckId)` | Hand \u2192 Deck |\n| `.moveCardsFromHandToHand(fromPlayer, fromHand, toPlayer, toHand, cardIds)` | Hand \u2192 Hand |\n| `.moveCardsToPlayer(cardIds, toPlayerId, handId)` | Any \u2192 Hand |\n| `.flip(deckId, cardId)` | Flip card face up/down |\n| `.detachCard(cardId)` | Remove card from all locations |\n| `.transferOwnership(cardId, toPlayer)` | Change card owner |\n\n#### Card movement semantics\n\n- `moveCardsFromHandToHand(fromPlayer, fromHand, toPlayer, toHand, cardIds)` is **additive** at destination.\n- Destination cards are preserved; moved cards are added to the destination hand.\n- This API does **not** replace/overwrite the destination hand.\n- For pass/rotate mechanics where each player should receive exactly one other player\'s hand, avoid in-place cyclic moves. Snapshot source hands first, then move via a temporary location (or other two-phase transfer pattern).\n\n### `apis.deckApi`\n\n| Method | Description |\n| ------------------------------------------------------------- | ------------------------- |\n| `.moveCardsFromDeckToPlayer(deckId, playerId, handId, count)` | Deck \u2192 Hand (deal) |\n| `.moveCardsFromDeckToDeck(fromDeckId, toDeckId)` | Deck \u2192 Deck |\n| `.shuffle(deckId)` | Shuffle a deck |\n| `.addCards(deckId, cardIds)` | Add cards to a deck |\n| `.removeCard(deckId, cardId)` | Remove a card from a deck |\n\n### `apis.gameApi`\n\n| Method | Description |\n| ---------------------------------- | ---------------------------------------------- |\n| `.setActivePlayers(playerIds)` | Set who can act (use in ALL_PLAYERS `onEnter`) |\n| `.setNextPlayer(playerId)` | Set single active player |\n| `.advanceTurn()` | Move to next player in order |\n| `.declareWinner(playerId, reason)` | Declare game winner |\n| `.endGame()` | End the game |\n\n### `apis.globalStateApi`\n\n| Method | Description |\n| ------------------------------------- | ---------------------------- |\n| `.setGlobalState(newState)` | Replace all global variables |\n| `.setPlayerState(playerId, newState)` | Replace a player\'s variables |\n\n### `apis.kvApi`\n\nInternal-only key-value store. **UI cannot access these values.**\n\n| Method | Returns | Description |\n| ------------------ | ---------------- | ----------------------------------- |\n| `.set(key, value)` | `KvSetResult` | Store a JSON value |\n| `.get(key)` | `KvGetResult` | Read a value (`.success`, `.value`) |\n| `.delete(key)` | `KvDeleteResult` | Remove a key |\n| `.has(key)` | `boolean` | Check existence |\n| `.keys()` | `string[]` | List all keys |\n\n### `apis.resourceApi`\n\n| Method | Description |\n| ------------------------- | -------------------------- |\n| `.add(playerId, cost)` | Give resources to player |\n| `.deduct(playerId, cost)` | Take resources from player |\n\n### `apis.dieApi`\n\n| Method | Description |\n| -------------------------- | ------------------------- |\n| `.roll(dieId)` | Roll a die |\n| `.setValue(dieId, value?)` | Set die to specific value |\n\n## Typed KV Store\n\nUse `createTypedKv` from `sdk/stateApi.js` for type-safe KV access:\n\n```typescript\nimport { createTypedKv } from "../sdk/stateApi.js";\n\ninterface MyKv {\n playersActed: PlayerId[];\n roundData: { scores: number[] };\n}\n\nconst kv = createTypedKv<MyKv>(apis.kvApi);\nkv.set("playersActed", ["player-1"]); // Type-checked key and value\nconst acted = kv.get("playersActed"); // PlayerId[] | null\nkv.has("playersActed"); // boolean\nkv.delete("playersActed"); // boolean (existed)\n```\n\n## Card `playedBy` Tracking\n\nWhen cards move from a Hand to a Deck via `moveCardsFromHandToDeck()`, the engine automatically records `playedBy` on each card:\n\n```typescript\n// Move card from player\'s hand to a shared deck\napis.cardApi.moveCardsFromHandToDeck(\n playerId,\n "main-hand",\n [cardId],\n "play-area",\n);\n\n// Later, check who played a specific card\nconst whoPlayed = state.card.getPlayedBy(cardId); // PlayerId | null\n```\n\n## Location Type\n\nCards have a location discriminated union:\n\n```typescript\ntype Location =\n | { type: "Detached" }\n | {\n type: "InDeck";\n deckId: DeckId;\n playedBy: PlayerId | null;\n position: number | null;\n }\n | {\n type: "InHand";\n handId: HandId;\n playerId: PlayerId;\n position: number | null;\n }\n | {\n type: "InZone";\n zoneId: string;\n playedBy: PlayerId | null;\n position: number | null;\n };\n```\n\n## Validation Helpers\n\n```typescript\nimport { validationSuccess, validationError } from "../sdk/validation.js";\n\n// Valid action\nreturn validationSuccess();\n\n// Invalid action \u2014 errorCode should be kebab-case\nreturn validationError(\n "must-play-valid-combination",\n "You must play a valid card combination",\n);\n```\n',
1938
1938
  "references/app-best-practices.md": '# App Best Practices\n\nUse this guide when implementing `app/phases/*.ts`.\n\nThis page is about phase logic and engine APIs. If you are still deciding the game model itself, fix `rule.md` or `manifest.json` first.\n\n## Before Writing Code\n\n1. Read `shared/manifest.ts` so you know the actual generated IDs and types.\n2. Read every file in `app/phases/` to understand the current flow.\n3. Review the generated SDK types in `app/sdk/`.\n\nIf the card model is still unclear, stop and revisit [hands-vs-decks.md](hands-vs-decks.md) and [manifest-authoring.md](manifest-authoring.md).\n\n## Core Principles\n\n1. **The app is authoritative**\n All gameplay mutations must go through `ctx.apis.*`. Do not assume the UI moved cards or tracked state correctly.\n2. **Prefer mutation results inside the current transaction**\n Values returned by mutation APIs are safer than immediately rereading `ctx.state.*`, which may still reflect the previous snapshot.\n3. **Use the right active-player API**\n Use `setActivePlayers()` for `ALL_PLAYERS` phases and `setNextPlayer()` for `SINGLE_PLAYER` phases.\n4. **All card movement goes through move APIs**\n Never hand-edit card locations in your own state.\n5. **Use generated IDs exactly**\n Deck IDs, hand IDs, resource IDs, board IDs, and action names should come from `shared/manifest.ts`.\n6. **KV is internal-only**\n `kvApi` is for app logic, not UI data. Pass UI-facing data through `getUIArgs()` or stored state.\n7. **Keep player-visible outcomes around long enough to render**\n If an `AUTO` phase resolves a trick, round, or reveal, preserve that result until the next player-facing phase can show it.\n\n## Validation\n\n- Implement `validateAction` for `SINGLE_PLAYER` and `ALL_PLAYERS` phases.\n- Return `validationSuccess()` for valid actions.\n- Return `validationError(code, message)` for invalid actions.\n- Keep error codes stable and kebab-case, for example `must-play-valid-combination`.\n\n## UI Args Contract\n\n- Define UI arg interfaces in `shared/ui-args.ts`.\n- Implement `getUIArgs(ctx, playerId)` for each player-facing phase.\n- Send only the data the UI actually needs.\n- Prefer computed values over forcing the UI to recompute them.\n\nExample:\n\n- better: `canPlayCard: boolean`\n- worse: raw state fragments that force the UI to rebuild game rules\n\n## Safety Habits\n\n- Check array length before indexing.\n- Use generated guards from `../generated/guards` for typed ID checks.\n- Avoid `any`.\n- Log compact payloads with `ctx.logger`, not giant arrays or full state dumps.\n\n## Common Patterns\n\n### Typed KV store\n\n```typescript\nimport { createTypedKv } from "../sdk/stateApi.js";\n\ninterface PhaseKv {\n playersActed: PlayerId[];\n roundNumber: number;\n}\n\nconst kv = createTypedKv<PhaseKv>(apis.kvApi);\nkv.set("playersActed", []);\nconst acted = kv.get("playersActed"); // PlayerId[] | null\n```\n\n### Card `playedBy` tracking\n\n```typescript\napis.cardApi.moveCardsFromHandToDeck(\n playerId,\n "main-hand",\n [cardId],\n "play-area",\n);\n\nconst whoPlayed = state.card.getPlayedBy(cardId); // PlayerId | null\n```\n\n### Turn advancement\n\n```typescript\nonAfterAction(ctx, playerId, actionType) {\n ctx.apis.gameApi.advanceTurn();\n},\n```\n\n### ALL_PLAYERS progress tracking\n\nUse the engine-managed helpers in [all-players-tracking.md](all-players-tracking.md) instead of building your own action-tracking store.\n',
1939
- "references/building-your-first-game.md": '# Building Your First Game\n\nUse this as the default end-to-end workflow for a new Dreamboard game. The goal is not just "something renders"; the goal is a complete, playable game with rules, app logic, UI, and tests all aligned.\n\n## Core Workflow\n\n1. Create a game: `dreamboard new <slug> --description "<<short description>>"`\n2. Author `rule.md` and `manifest.json`\n Quality bar: define the full game loop up front, including setup, turn structure, win/loss conditions, scoring, tie behavior, hidden/public information, and every player action the app must support. `manifest.json` should reflect the real player counts, entities, zones, and controls needed by the rules, not a partial guess.\n Refer to\n - [references/rule-authoring.md](rule-authoring.md),\n - [references/manifest-authoring.md](manifest-authoring.md)\n3. Run `dreamboard update` after every manifest or rule change\n Quality bar: do this immediately after each rule or manifest edit so generated scaffold, types, and framework assumptions stay in sync. Do not keep coding against stale generated files.\n4. Implement rules in `app/phases/*.ts` and UI data in `shared/ui-args.ts`\n Quality bar: every gameplay phase must be implemented end to end. Cover all rule branches, all legal player actions, all transitions, all round-resolution logic, and all game-end paths. `shared/ui-args.ts` must provide enough data for the UI to render every playable state without guessing or reconstructing hidden game logic in React.\n Refer to\n - [references/phase-handlers.md](phase-handlers.md),\n - [references/api-reference.md](api-reference.md),\n - [references/hands-vs-decks.md](hands-vs-decks.md),\n - [references/all-players-tracking.md](all-players-tracking.md),\n - [references/app-best-practices.md](app-best-practices.md)\n5. Implement UI in `ui/App.tsx` and `ui/components/*`\n Quality bar: the entire game must be playable through the UI, including setup, core turns, confirmations, resolution states, errors and disabled states, and end-of-game feedback. Follow the styling requirements in [ui-style-guide.md](ui-style-guide.md). Avoid half-finished interfaces such as raw debug JSON, missing controls, placeholder labels, or flows that require reading logs to understand what to do next.\n Refer to\n - [references/ui-best-practices.md](ui-best-practices.md)\n6. Typecheck from the game root: `bun install && bun run typecheck`\n Quality bar: typecheck must pass cleanly from the game root with no ignored errors and no dead code left behind from iteration.\n7. Push compiled changes: `dreamboard push`\n Quality bar: the pushed build should reflect the same rules and UI you validated locally, with no pending local-only fixes or known desync between authored files and compiled output.\n8. Generate deterministic bases for testing: `dreamboard test generate`\n Quality bar: regenerate bases whenever initial setup, compiled behavior, or game identity changes. The generated bases should be stable enough that regressions are easy to spot.\n9. Run regression scenarios: `dreamboard test run`\n Quality bar: cover the happy path, invalid actions, boundary conditions, phase transitions, simultaneous or multi-player interactions where relevant, and every game-ending outcome. All scenario statuses should be green.\n Refer to\n - [references/test-harness.md](test-harness.md)\n10. Validate the runtime behavior and live UI with `dreamboard run --scenario <scenario_file>` and `dreamboard run --screenshot`\n Quality bar: if Playwright is installed locally, screenshot verification is required. Confirm the UI looks complete and usable in real runtime states, including the initial view, an active turn, resolution moments, and the end-game state. Fix any broken layout, missing affordances, or styling drift before considering the game done.\n Refer to\n - [references/adversarial-testing.md](adversarial-testing.md)\n\nDo not treat UI, rules, and tests as separate finish-later tracks. A game is only complete when the authored rules, app logic, UI flow, pushed build, and regression coverage all agree on the same behavior.\n\n## What "Done" Means\n\n- App and logic are complete: all phases are implemented, all rule branches are covered, and no required gameplay path is left as a TODO or inferred only in the UI.\n- UI is complete: the full game is playable without relying on logs, debug panels, or manual backend intervention.\n- Styling is complete: the game follows [ui-style-guide.md](ui-style-guide.md) rather than shipping a generic or half-finished interface.\n- Verification is complete: typecheck passes, regression scenarios are green, and runtime screenshot verification has been performed when Playwright is available.\n',
1939
+ "references/building-your-first-game.md": '# Building Your First Game\n\nUse this as the default end-to-end workflow for a new Dreamboard game. The goal is not just "something renders"; the goal is a complete, playable game with rules, app logic, UI, and tests all aligned.\n\n## Core Workflow\n\n1. Create a game: `dreamboard new <slug> --description "<<short description>>"`\n2. Author `rule.md` and `manifest.json`\n Quality bar: define the full game loop up front, including setup, turn structure, win/loss conditions, scoring, tie behavior, hidden/public information, and every player action the app must support. `manifest.json` should reflect the real player counts, entities, zones, and controls needed by the rules, not a partial guess.\n Refer to\n - [references/rule-authoring.md](rule-authoring.md),\n - [references/manifest-authoring.md](manifest-authoring.md)\n3. Run `dreamboard sync` after every manifest or rule change\n Quality bar: do this immediately after each rule or manifest edit so generated scaffold, types, and remote authored state stay in sync. Do not keep coding against stale generated files.\n4. Implement rules in `app/phases/*.ts` and UI data in `shared/ui-args.ts`\n Quality bar: every gameplay phase must be implemented end to end. Cover all rule branches, all legal player actions, all transitions, all round-resolution logic, and all game-end paths. `shared/ui-args.ts` must provide enough data for the UI to render every playable state without guessing or reconstructing hidden game logic in React.\n Refer to\n - [references/phase-handlers.md](phase-handlers.md),\n - [references/api-reference.md](api-reference.md),\n - [references/hands-vs-decks.md](hands-vs-decks.md),\n - [references/all-players-tracking.md](all-players-tracking.md),\n - [references/app-best-practices.md](app-best-practices.md)\n5. Implement UI in `ui/App.tsx` and `ui/components/*`\n Quality bar: the entire game must be playable through the UI, including setup, core turns, confirmations, resolution states, errors and disabled states, and end-of-game feedback. Follow the styling requirements in [ui-style-guide.md](ui-style-guide.md). Avoid half-finished interfaces such as raw debug JSON, missing controls, placeholder labels, or flows that require reading logs to understand what to do next.\n Refer to\n - [references/ui-best-practices.md](ui-best-practices.md)\n6. Typecheck from the game root: `npm install && npm run typecheck`\n Quality bar: typecheck must pass cleanly from the game root with no ignored errors and no dead code left behind from iteration.\n7. Compile the current authored state: `dreamboard compile`\n Quality bar: the compiled build should reflect the same rules and UI you validated locally, with no pending local-only fixes or known desync between authored files and the remote authored state.\n8. Generate deterministic bases for testing: `dreamboard test generate`\n Quality bar: regenerate bases whenever initial setup, compiled behavior, or game identity changes. The generated bases should be stable enough that regressions are easy to spot.\n9. Run regression scenarios: `dreamboard test run`\n Quality bar: cover the happy path, invalid actions, boundary conditions, phase transitions, simultaneous or multi-player interactions where relevant, and every game-ending outcome. All scenario statuses should be green.\n Refer to\n - [references/test-harness.md](test-harness.md)\n10. Validate the runtime behavior and live UI with `dreamboard run --scenario <scenario_file>` and `dreamboard run --screenshot`\n Quality bar: if Playwright is installed locally, screenshot verification is required. Confirm the UI looks complete and usable in real runtime states, including the initial view, an active turn, resolution moments, and the end-game state. Fix any broken layout, missing affordances, or styling drift before considering the game done.\n Refer to\n - [references/adversarial-testing.md](adversarial-testing.md)\n\nDo not treat UI, rules, and tests as separate finish-later tracks. A game is only complete when the authored rules, app logic, UI flow, compiled build, and regression coverage all agree on the same behavior.\n\n## What "Done" Means\n\n- App and logic are complete: all phases are implemented, all rule branches are covered, and no required gameplay path is left as a TODO or inferred only in the UI.\n- UI is complete: the full game is playable without relying on logs, debug panels, or manual backend intervention.\n- Styling is complete: the game follows [ui-style-guide.md](ui-style-guide.md) rather than shipping a generic or half-finished interface.\n- Verification is complete: typecheck passes, regression scenarios are green, and runtime screenshot verification has been performed when Playwright is available.\n',
1940
1940
  "references/hands-vs-decks.md": '# Hands vs Decks\n\nUse this guide when you are deciding how to model a card container in `manifest.json`.\n\nDreamboard separates three ideas that are easy to blur together in tabletop rules:\n\n- `cardSets`: what cards exist\n- `decks`: shared card containers\n- `playerHandDefinitions`: per-player card containers\n\nFor the full manifest schema, see [manifest-authoring.md](manifest-authoring.md).\n\n## Quick Decision Rule\n\n1. If you are defining the cards themselves, use a `cardSet`.\n2. If the whole table shares one container, use a `deck`.\n3. If each player gets their own copy of the container, use a `playerHandDefinition`.\n\n## Canonical Meanings\n\n| Concept | Manifest key | Runtime ID | Use it for |\n| -------- | ----------------------- | ------------- | --------------------------------- |\n| Card set | `cardSets` | none directly | Card content and card schema |\n| Hand | `playerHandDefinitions` | `HandId` | Cards each player owns separately |\n| Deck | `decks` | `DeckId` | Shared piles or shared card areas |\n\n## Naming Rule\n\nUse the Dreamboard meaning, not the tabletop nickname.\n\nIf a real-world game says each player has a "deck", but each player owns a separate pile, model it as a hand.\n\nExamples:\n\n- a player\'s draw pile in War is a hand\n- a player\'s scored pile is a hand\n- the shared draw pile in Poker is a deck\n- the shared discard pile is a deck\n- a shared trick area is a deck\n\n## Common Mistakes\n\n- Do not use a deck just because cards are face-down.\n- Do not use a deck for something every player owns separately.\n- Do not use a hand for a communal area just because cards stay there for a while.\n\nVisibility is separate from ownership:\n\n- if each player has their own container, it is still a hand\n- set `visibility: "public"` if other players should be able to see that hand\n\n## Example\n\n```json\n{\n "cardSets": [\n {\n "type": "preset",\n "id": "standard_52_deck",\n "name": "Standard 52-Card Deck"\n }\n ],\n "decks": [\n {\n "id": "main-deck",\n "name": "Main Deck",\n "cardSetId": "standard_52_deck"\n },\n {\n "id": "battle-zone",\n "name": "Battle Zone",\n "cardSetId": "standard_52_deck"\n }\n ],\n "playerHandDefinitions": [\n {\n "id": "player-deck",\n "displayName": "Your Deck",\n "visibility": "ownerOnly",\n "cardSetIds": ["standard_52_deck"]\n },\n {\n "id": "won-pile",\n "displayName": "Won Cards",\n "visibility": "public",\n "cardSetIds": ["standard_52_deck"]\n }\n ]\n}\n```\n\nIn that example:\n\n- `standard_52_deck` defines the cards\n- `main-deck` and `battle-zone` are shared decks\n- `player-deck` and `won-pile` are per-player hands\n\n## Related Move APIs\n\n```typescript\n// Hand -> Deck\napis.cardApi.moveCardsFromHandToDeck(playerId, handId, cardIds, deckId);\n\n// Deck -> Hand\napis.deckApi.moveCardsFromDeckToPlayer(deckId, playerId, handId, count);\n\n// Hand -> Hand\napis.cardApi.moveCardsFromHandToHand(\n fromPlayer,\n fromHand,\n toPlayer,\n toHand,\n cardIds,\n);\n\n// Deck -> Deck\napis.deckApi.moveCardsFromDeckToDeck(fromDeckId, toDeckId);\n```\n\n## Rule Of Thumb\n\n- shared container: `deck`\n- per-player container: `playerHandDefinition`\n- card blueprint: `cardSet`\n',
1941
- "references/manifest-authoring.md": '# Manifest Authoring Guide\n\nUse this guide when editing `manifest.json`.\n\n`manifest.json` describes the runtime shape of the game:\n\n- who can play\n- what cards, resources, dice, and boards exist\n- what actions players can submit\n- what phases and stored state the engine needs\n\nKeep the full game explanation in `rule.md`. Use the manifest to turn that explanation into typed runtime data.\n\nAfter editing the manifest, run `dreamboard update` to regenerate scaffolded files such as `shared/manifest.ts` and `app/phases/`.\n\nIf you are still deciding whether something is a hand, a deck, or a card set, read [hands-vs-decks.md](hands-vs-decks.md) alongside this guide.\n\n## Supported Top-Level Shape\n\n```json\n{\n "playerConfig": { ... },\n "cardSets": [ ... ],\n "playerHandDefinitions": [ ... ],\n "decks": [ ... ],\n "dice": [ ... ],\n "resources": [ ... ],\n "boardDefinitions": [ ... ],\n "availableActions": [ ... ],\n "stateMachine": { ... },\n "variableSchema": { ... }\n}\n```\n\n| Field | Required | Purpose |\n| ----------------------- | -------- | -------------------------------------------- |\n| `playerConfig` | \u2705 | Supported player counts |\n| `cardSets` | \u2705 | Card blueprints and card property schemas |\n| `playerHandDefinitions` | \u2705 | Per-player card containers |\n| `decks` | \u274C | Shared card containers |\n| `dice` | \u274C | Shared dice |\n| `resources` | \u274C | Typed resource economy |\n| `boardDefinitions` | \u274C | Structured boards such as hex, square, track |\n| `availableActions` | \u2705 | Player-submittable actions |\n| `stateMachine` | \u2705 | Game phases and transitions |\n| `variableSchema` | \u2705 | Global and per-player stored state |\n\n## Authoring Rules\n\n- Only use documented schema fields. If a key is not described in this guide, do not invent it in `manifest.json`.\n- Optional list fields should be omitted or set to `[]`. Do not use `null`.\n- Keep the manifest about runtime structure. Put presentation choices in UI code or `shared/ui-args.ts`.\n- There is no top-level `version` field in the manifest schema.\n- A deck entry is structural data only. Use `id`, `name`, and `cardSetId`.\n\n## Recommended Authoring Order\n\n1. Start from `rule.md`.\n2. Define the game\'s containers and content: players, card sets, hands, decks, resources, dice, and boards.\n3. Define the actions players can submit.\n4. Define the phase flow in `stateMachine`.\n5. Add only the stored state that cannot be derived from the rest of the game state.\n\n## 1. `playerConfig`\n\nUse `playerConfig` to declare the supported player counts.\n\n```json\n{\n "playerConfig": {\n "minPlayers": 2,\n "maxPlayers": 4,\n "optimalPlayers": 4\n }\n}\n```\n\n## 2. `cardSets`\n\n`cardSets` define the cards that exist in the game. They do not describe where those cards live during play.\n\nThere are two card set styles:\n\n- `preset` for built-in sets such as a standard 52-card deck\n- `manual` for authored card content and custom card schemas\n\n### Preset card set\n\n```json\n{\n "cardSets": [\n {\n "type": "preset",\n "id": "standard_52_deck",\n "name": "Standard 52-Card Deck"\n }\n ]\n}\n```\n\n### Manual card set\n\n```json\n{\n "type": "manual",\n "id": "resource-cards",\n "name": "Resource Cards",\n "cardSchema": {\n "properties": {\n "value": { "type": "integer", "description": "Point value" },\n "category": { "type": "string", "description": "Resource category" }\n }\n },\n "cards": [\n {\n "type": "lumber",\n "name": "Lumber",\n "count": 4,\n "properties": { "value": "1", "category": "wood" }\n }\n ]\n}\n```\n\nCard authoring reminders:\n\n- `type` is the manifest-level card type.\n- `count` is the number of copies to create.\n- runtime card IDs are derived from `type` and `count` (`lumber`, `lumber-2`, `lumber-3`, ...)\n- `properties` must match `cardSchema`\n- `name` and `text` are worth keeping because they help scaffolding, debugging, and agent reasoning\n\n## 3. `playerHandDefinitions`\n\nEach `playerHandDefinition` creates one container per player.\n\nUse a hand for anything each player owns separately, even if the tabletop rules call it a "deck", "reserve", or "scored pile".\n\n```json\n{\n "playerHandDefinitions": [\n {\n "id": "main-hand",\n "displayName": "Hand",\n "visibility": "ownerOnly",\n "maxCards": 7,\n "cardSetIds": ["standard_52_deck"]\n }\n ]\n}\n```\n\n| Field | Required | Purpose |\n| ------------- | -------- | ----------------------------------------------- |\n| `id` | \u2705 | Stable hand ID (`HandId`) |\n| `displayName` | \u2705 | Human-readable name |\n| `visibility` | \u274C | `ownerOnly`, `public`, or `hidden` |\n| `maxCards` | \u274C | Maximum allowed card count |\n| `minCards` | \u274C | Minimum required card count |\n| `cardSetIds` | \u274C | Allowed card sets; omit for any compatible card |\n| `description` | \u274C | Explanation for authors and tools |\n\n## 4. `decks`\n\n`decks` are shared card containers.\n\nUse a deck for any card location the whole table shares: a draw pile, discard pile, market row, trick area, or communal play area.\n\n```json\n{\n "decks": [\n {\n "id": "draw-pile",\n "name": "Draw Pile",\n "cardSetId": "standard_52_deck"\n },\n {\n "id": "discard-pile",\n "name": "Discard Pile",\n "cardSetId": "standard_52_deck"\n }\n ]\n}\n```\n\n| Field | Required | Purpose |\n| ----------- | -------- | -------------------------------------- |\n| `id` | \u2705 | Stable deck ID (`DeckId`) |\n| `name` | \u2705 | Human-readable name |\n| `cardSetId` | \u2705 | Card set used by this shared container |\n\n## 5. `dice`, `resources`, and `boardDefinitions`\n\nAdd these sections only if the game needs them.\n\n- `dice` define shared dice such as `d6` or custom dice.\n- `resources` define typed player economies such as coins, food, or energy.\n- `boardDefinitions` define structured boards such as hex maps, square grids, tracks, or networks.\n\nExample:\n\n```json\n{\n "dice": [{ "id": "d6", "name": "Six-Sided Die", "sides": 6 }],\n "resources": [{ "id": "coins", "displayName": "Coins" }]\n}\n```\n\n## 6. `availableActions`\n\nActions describe what players may submit to the engine.\n\nThink of actions as structured intent, not UI clicks. The UI gathers selections, then submits those values through action parameters.\n\n```json\n{\n "availableActions": [\n {\n "actionType": "playCard",\n "displayName": "Play Card",\n "description": "Play the selected card",\n "parameters": [\n {\n "name": "cardId",\n "type": "cardId",\n "required": true,\n "cardSetId": "standard_52_deck"\n }\n ],\n "errorCodes": ["NOT_YOUR_TURN", "INVALID_CARD"]\n }\n ]\n}\n```\n\nAction authoring reminders:\n\n- use stable, readable IDs\n- use `cardSetId` when a `cardId` or `cardType` parameter should be limited to one card set\n- use `array: true` with `minLength` and `maxLength` when an action takes multiple values\n\n## 7. `stateMachine`\n\n`stateMachine` defines the phase flow of the game.\n\nUse it to name phases, describe who can act, and define how the game moves from one phase to the next.\n\nState types:\n\n- `AUTO`: engine-driven phase\n- `SINGLE_PLAYER`: one active player acts\n- `ALL_PLAYERS`: multiple players may act in parallel\n\n```json\n{\n "stateMachine": {\n "initialState": "dealCards",\n "states": [\n {\n "name": "dealCards",\n "type": "AUTO",\n "description": "Shuffle and deal cards.",\n "transitions": [{ "targetState": "playTurn" }]\n },\n {\n "name": "playTurn",\n "type": "SINGLE_PLAYER",\n "description": "Active player plays a card.",\n "availableActions": ["playCard"],\n "transitions": [{ "targetState": "playTurn" }]\n }\n ]\n }\n}\n```\n\n## 8. `variableSchema`\n\n`variableSchema` is for state that must be stored explicitly.\n\nKeep it small. If something can be derived from card locations, resources, board state, or the current phase, it usually does not belong here.\n\nGood candidates:\n\n- running scores across rounds\n- round number, trump suit, current bid\n- flags that affect future legal moves\n\nAvoid storing:\n\n- counts that can be derived from hands or decks\n- phase flow already represented by `stateMachine`\n- duplicated resource totals\n\n```json\n{\n "variableSchema": {\n "globalVariableSchema": {\n "properties": {\n "roundNumber": { "type": "integer", "description": "Current round" }\n }\n },\n "playerVariableSchema": {\n "properties": {\n "score": {\n "type": "integer",\n "description": "Accumulated score"\n }\n }\n }\n }\n}\n```\n\n## Final Checklist\n\nBefore running `dreamboard update`, check that:\n\n- every referenced `cardSetId`, `handId`, `deckId`, resource ID, board ID, and action name exists\n- the manifest only uses documented schema fields\n- `variableSchema` stores only non-derivable state\n- optional list fields are omitted or `[]`, not `null`\n\nThen run:\n\n```bash\ndreamboard update\n```\n',
1941
+ "references/manifest-authoring.md": '# Manifest Authoring Guide\n\nUse this guide when editing `manifest.json`.\n\n`manifest.json` describes the runtime shape of the game:\n\n- who can play\n- what cards, resources, dice, and boards exist\n- what actions players can submit\n- what phases and stored state the engine needs\n\nKeep the full game explanation in `rule.md`. Use the manifest to turn that explanation into typed runtime data.\n\nAfter editing the manifest, run `dreamboard sync` to regenerate scaffolded files such as `shared/manifest.ts` and `app/phases/`.\n\nIf you are still deciding whether something is a hand, a deck, or a card set, read [hands-vs-decks.md](hands-vs-decks.md) alongside this guide.\n\n## Supported Top-Level Shape\n\n```json\n{\n "playerConfig": { ... },\n "cardSets": [ ... ],\n "playerHandDefinitions": [ ... ],\n "decks": [ ... ],\n "dice": [ ... ],\n "resources": [ ... ],\n "boardDefinitions": [ ... ],\n "availableActions": [ ... ],\n "stateMachine": { ... },\n "variableSchema": { ... }\n}\n```\n\n| Field | Required | Purpose |\n| ----------------------- | -------- | -------------------------------------------- |\n| `playerConfig` | \u2705 | Supported player counts |\n| `cardSets` | \u2705 | Card blueprints and card property schemas |\n| `playerHandDefinitions` | \u2705 | Per-player card containers |\n| `decks` | \u274C | Shared card containers |\n| `dice` | \u274C | Shared dice |\n| `resources` | \u274C | Typed resource economy |\n| `boardDefinitions` | \u274C | Structured boards such as hex, square, track |\n| `availableActions` | \u2705 | Player-submittable actions |\n| `stateMachine` | \u2705 | Game phases and transitions |\n| `variableSchema` | \u2705 | Global and per-player stored state |\n\n## Authoring Rules\n\n- Only use documented schema fields. If a key is not described in this guide, do not invent it in `manifest.json`.\n- Optional list fields should be omitted or set to `[]`. Do not use `null`.\n- Keep the manifest about runtime structure. Put presentation choices in UI code or `shared/ui-args.ts`.\n- There is no top-level `version` field in the manifest schema.\n- A deck entry is structural data only. Use `id`, `name`, and `cardSetId`.\n\n## Recommended Authoring Order\n\n1. Start from `rule.md`.\n2. Define the game\'s containers and content: players, card sets, hands, decks, resources, dice, and boards.\n3. Define the actions players can submit.\n4. Define the phase flow in `stateMachine`.\n5. Add only the stored state that cannot be derived from the rest of the game state.\n\n## 1. `playerConfig`\n\nUse `playerConfig` to declare the supported player counts.\n\n```json\n{\n "playerConfig": {\n "minPlayers": 2,\n "maxPlayers": 4,\n "optimalPlayers": 4\n }\n}\n```\n\n## 2. `cardSets`\n\n`cardSets` define the cards that exist in the game. They do not describe where those cards live during play.\n\nThere are two card set styles:\n\n- `preset` for built-in sets such as a standard 52-card deck\n- `manual` for authored card content and custom card schemas\n\n### Preset card set\n\n```json\n{\n "cardSets": [\n {\n "type": "preset",\n "id": "standard_52_deck",\n "name": "Standard 52-Card Deck"\n }\n ]\n}\n```\n\n### Manual card set\n\n```json\n{\n "type": "manual",\n "id": "resource-cards",\n "name": "Resource Cards",\n "cardSchema": {\n "properties": {\n "value": { "type": "integer", "description": "Point value" },\n "category": { "type": "string", "description": "Resource category" }\n }\n },\n "cards": [\n {\n "type": "lumber",\n "name": "Lumber",\n "count": 4,\n "properties": { "value": "1", "category": "wood" }\n }\n ]\n}\n```\n\nCard authoring reminders:\n\n- `type` is the manifest-level card type.\n- `count` is the number of copies to create.\n- runtime card IDs are derived from `type` and `count` (`lumber`, `lumber-2`, `lumber-3`, ...)\n- `properties` must match `cardSchema`\n- `name` and `text` are worth keeping because they help scaffolding, debugging, and agent reasoning\n\n## 3. `playerHandDefinitions`\n\nEach `playerHandDefinition` creates one container per player.\n\nUse a hand for anything each player owns separately, even if the tabletop rules call it a "deck", "reserve", or "scored pile".\n\n```json\n{\n "playerHandDefinitions": [\n {\n "id": "main-hand",\n "displayName": "Hand",\n "visibility": "ownerOnly",\n "maxCards": 7,\n "cardSetIds": ["standard_52_deck"]\n }\n ]\n}\n```\n\n| Field | Required | Purpose |\n| ------------- | -------- | ----------------------------------------------- |\n| `id` | \u2705 | Stable hand ID (`HandId`) |\n| `displayName` | \u2705 | Human-readable name |\n| `visibility` | \u274C | `ownerOnly`, `public`, or `hidden` |\n| `maxCards` | \u274C | Maximum allowed card count |\n| `minCards` | \u274C | Minimum required card count |\n| `cardSetIds` | \u274C | Allowed card sets; omit for any compatible card |\n| `description` | \u274C | Explanation for authors and tools |\n\n## 4. `decks`\n\n`decks` are shared card containers.\n\nUse a deck for any card location the whole table shares: a draw pile, discard pile, market row, trick area, or communal play area.\n\n```json\n{\n "decks": [\n {\n "id": "draw-pile",\n "name": "Draw Pile",\n "cardSetId": "standard_52_deck"\n },\n {\n "id": "discard-pile",\n "name": "Discard Pile",\n "cardSetId": "standard_52_deck"\n }\n ]\n}\n```\n\n| Field | Required | Purpose |\n| ----------- | -------- | -------------------------------------- |\n| `id` | \u2705 | Stable deck ID (`DeckId`) |\n| `name` | \u2705 | Human-readable name |\n| `cardSetId` | \u2705 | Card set used by this shared container |\n\n## 5. `dice`, `resources`, and `boardDefinitions`\n\nAdd these sections only if the game needs them.\n\n- `dice` define shared dice such as `d6` or custom dice.\n- `resources` define typed player economies such as coins, food, or energy.\n- `boardDefinitions` define structured boards such as hex maps, square grids, tracks, or networks.\n\nExample:\n\n```json\n{\n "dice": [{ "id": "d6", "name": "Six-Sided Die", "sides": 6 }],\n "resources": [{ "id": "coins", "displayName": "Coins" }]\n}\n```\n\n## 6. `availableActions`\n\nActions describe what players may submit to the engine.\n\nThink of actions as structured intent, not UI clicks. The UI gathers selections, then submits those values through action parameters.\n\n```json\n{\n "availableActions": [\n {\n "actionType": "playCard",\n "displayName": "Play Card",\n "description": "Play the selected card",\n "parameters": [\n {\n "name": "cardId",\n "type": "cardId",\n "required": true,\n "cardSetId": "standard_52_deck"\n }\n ],\n "errorCodes": ["NOT_YOUR_TURN", "INVALID_CARD"]\n }\n ]\n}\n```\n\nAction authoring reminders:\n\n- use stable, readable IDs\n- use `cardSetId` when a `cardId` or `cardType` parameter should be limited to one card set\n- use `array: true` with `minLength` and `maxLength` when an action takes multiple values\n\n## 7. `stateMachine`\n\n`stateMachine` defines the phase flow of the game.\n\nUse it to name phases, describe who can act, and define how the game moves from one phase to the next.\n\nState types:\n\n- `AUTO`: engine-driven phase\n- `SINGLE_PLAYER`: one active player acts\n- `ALL_PLAYERS`: multiple players may act in parallel\n\n```json\n{\n "stateMachine": {\n "initialState": "dealCards",\n "states": [\n {\n "name": "dealCards",\n "type": "AUTO",\n "description": "Shuffle and deal cards.",\n "transitions": [{ "targetState": "playTurn" }]\n },\n {\n "name": "playTurn",\n "type": "SINGLE_PLAYER",\n "description": "Active player plays a card.",\n "availableActions": ["playCard"],\n "transitions": [{ "targetState": "playTurn" }]\n }\n ]\n }\n}\n```\n\n## 8. `variableSchema`\n\n`variableSchema` is for state that must be stored explicitly.\n\nKeep it small. If something can be derived from card locations, resources, board state, or the current phase, it usually does not belong here.\n\nGood candidates:\n\n- running scores across rounds\n- round number, trump suit, current bid\n- flags that affect future legal moves\n\nAvoid storing:\n\n- counts that can be derived from hands or decks\n- phase flow already represented by `stateMachine`\n- duplicated resource totals\n\n```json\n{\n "variableSchema": {\n "globalVariableSchema": {\n "properties": {\n "roundNumber": { "type": "integer", "description": "Current round" }\n }\n },\n "playerVariableSchema": {\n "properties": {\n "score": {\n "type": "integer",\n "description": "Accumulated score"\n }\n }\n }\n }\n}\n```\n\n## Final Checklist\n\nBefore running `dreamboard sync`, check that:\n\n- every referenced `cardSetId`, `handId`, `deckId`, resource ID, board ID, and action name exists\n- the manifest only uses documented schema fields\n- `variableSchema` stores only non-derivable state\n- optional list fields are omitted or `[]`, not `null`\n\nThen run:\n\n```bash\ndreamboard sync\n```\n',
1942
1942
  "references/phase-handlers.md": '# Phase Handlers\n\nPhase handlers are the core abstraction for implementing game logic. Each state in the manifest\'s `stateMachine` maps to a phase handler file in `app/phases/`.\n\n## Phase Types\n\n| Type | File Import | When to use |\n| --------------- | --------------------------- | ------------------------------------------------------------ |\n| `AUTO` | `createAutoPhase` | No player input needed (deal, resolve, score) |\n| `SINGLE_PLAYER` | `createSinglePlayerPhase` | One player acts at a time (play a card, roll dice) |\n| `ALL_PLAYERS` | `createMultiplePlayerPhase` | All players act simultaneously (reveal, pass cards, discard) |\n\n## Phase Lifecycle\n\n**AUTO**: `execute(ctx)` \u2192 returns next state name\n\n**SINGLE_PLAYER / ALL_PLAYERS**:\n\n1. `onEnter(ctx)` \u2014 initialize phase state, set active players\n2. `validateAction(ctx, playerId, actionType, parameters)` \u2014 validate before execution\n3. `onPlayerAction(ctx, playerId, actionType, parameters)` \u2014 process the action (mutate state)\n4. `onAfterAction(ctx, playerId, actionType)` \u2014 per-action side effects (e.g., advance turn)\n5. `checkCompletion(ctx)` \u2014 return `null` if not done, or the next state name if done\n6. `onComplete(ctx)` \u2014 cleanup after transition decision\n\n## PhaseContext (`ctx`)\n\n```typescript\ninterface PhaseContext {\n readonly state: StateApi; // Read game state\n readonly apis: GameApis; // Mutate game state\n readonly logger: Logger; // Log messages\n}\n```\n\n## AUTO Phase Example\n\n```typescript\nimport type { AutoPhaseDefinition } from "../sdk/phaseHandlers";\nimport { createAutoPhase } from "../sdk/phaseHandlers";\n\nconst phaseDefinition = {\n execute(ctx) {\n const { state, apis, logger } = ctx;\n apis.deckApi.shuffle("main-deck");\n for (const playerId of state.player.getOrder()) {\n apis.deckApi.moveCardsFromDeckToPlayer(\n "main-deck",\n playerId,\n "main-hand",\n 5,\n );\n }\n return "playPhase";\n },\n} satisfies AutoPhaseDefinition<"dealCards">;\n\nexport const phase = createAutoPhase(phaseDefinition);\n```\n\n## SINGLE_PLAYER Phase Example\n\n```typescript\nimport type { SinglePlayerPhaseDefinition } from "../sdk/phaseHandlers";\nimport { createSinglePlayerPhase } from "../sdk/phaseHandlers";\nimport { validationSuccess, validationError } from "../sdk/validation.js";\n\nconst phaseDefinition = {\n onEnter(ctx) {\n const { state, apis } = ctx;\n const firstPlayer = state.player.getOrder()[0];\n apis.gameApi.setNextPlayer(firstPlayer);\n },\n\n validateAction(ctx, playerId, actionType, parameters) {\n return validationSuccess();\n },\n\n onPlayerAction(ctx, playerId, actionType, parameters) {\n const { apis } = ctx;\n // Process action...\n },\n\n onAfterAction(ctx, playerId, actionType) {\n const { apis } = ctx;\n apis.gameApi.advanceTurn(); // Move to next player\n },\n\n checkCompletion(ctx) {\n // Return null to keep playing, or next state name to transition\n return null;\n },\n\n getUIArgs(ctx, playerId) {\n return {};\n },\n} satisfies SinglePlayerPhaseDefinition<"playPhase", "playCard">;\n\nexport const phase = createSinglePlayerPhase(phaseDefinition);\n```\n\n## ALL_PLAYERS Phase Example\n\nSee [all-players-tracking.md](all-players-tracking.md) for the recommended tracking pattern.\n\n## Terminal Phase (Game End)\n\nFor states with no transitions (game end), use `TerminalPhaseDefinition`:\n\n```typescript\nimport type { TerminalPhaseDefinition } from "../sdk/phaseHandlers";\nimport { createTerminalPhase } from "../sdk/phaseHandlers";\n\nconst phaseDefinition = {\n execute(ctx) {\n const { state, apis } = ctx;\n // Optionally declare winner\n apis.gameApi.declareWinner("player-1", "Most points");\n apis.gameApi.endGame();\n return "endGame";\n },\n} satisfies TerminalPhaseDefinition<"endGame">;\n\nexport const phase = createTerminalPhase(phaseDefinition);\n```\n\n## Type Safety\n\nPhase definitions use `satisfies` for type checking:\n\n- `AutoPhaseDefinition<\'stateName\'>` \u2014 state name from manifest\n- `SinglePlayerPhaseDefinition<\'stateName\', \'actionName\'>` \u2014 with action type union\n- `MultiplePlayerPhaseDefinition<\'stateName\', \'actionName\'>` \u2014 for ALL_PLAYERS\n- Union multiple actions: `\'playCard\' | \'drawCard\'`\n',
1943
- "references/rule-authoring.md": '# Rule Authoring Guide\n\nUse this guide when creating or rewriting `rule.md`.\n\n`rule.md` should explain how the game works in plain language. It is the source of intent for the game. `manifest.json` comes later and turns that intent into runtime structure.\n\nKeep the jobs separate:\n\n- `rule.md` explains the rules\n- `manifest.json` defines the runtime model\n- `app/phases/*.ts` implements the rules\n\nIf you need schema details, use [manifest-authoring.md](manifest-authoring.md). If you need help deciding between hands and decks, use [hands-vs-decks.md](hands-vs-decks.md).\n\n## Recommended Document Structure\n\nUse this section order in `rule.md`.\n\n1. **Overview**\n\n - Game premise and objective\n - Player count\n - Match length target\n\n2. **Components**\n\n - Cards, hands, decks, tokens, dice, boards, resources\n - Public vs hidden information\n - Limits such as hand size or token caps\n\n3. **Setup**\n\n - Starting layout\n - Starting player and turn order\n - Initial cards, resources, and other player state\n\n4. **Gameplay**\n\n - Turn or phase loop in order\n - Available actions in each phase\n - Validation constraints\n - Transition conditions between phases\n\n5. **Scoring and Progression**\n\n - How points or progress are gained or lost\n - When scoring happens\n - Tie-breakers\n\n6. **Winning Conditions**\n\n - Exact end-game triggers\n - Winner resolution rules\n\n7. **Special Rules and Edge Cases**\n\n - Simultaneous actions\n - Empty deck behavior\n - Invalid or no-op actions\n - What happens when a player cannot act\n\n## Writing Rules That Map Cleanly To The Engine\n\n- **Phase model**\n State whether a phase is automatic, single-player, or all-players, and say how it ends.\n- **Action model**\n Name each action, say what it does, and list required inputs and constraints.\n- **State model**\n Separate stored state from derived state. If something can be computed from cards, resources, or the current phase, call that out.\n- **Visibility model**\n Say who can see what, when hidden information becomes public, and when reveals happen.\n- **Determinism**\n Resolve ambiguous cases with explicit priority order. Define random events precisely.\n\n## Writing Style\n\n- Use `must` for mandatory behavior.\n- Use `may` only for optional player choices.\n- Define terms once, then reuse the same words consistently.\n- Avoid UI-specific verbs such as "click" or "drag".\n\n## High-Signal Template\n\nUse this template when creating or rewriting `rule.md`:\n\n```markdown\n# <Game Name>\n\n## Overview\n\n- Players: <min-max> (optimal: <n>)\n- Objective: <how to win>\n- Duration: <target minutes>\n\n## Components\n\n- Card sets:\n- Shared decks:\n- Player hands:\n- Boards:\n- Resources, tokens, or dice:\n- Public vs hidden information:\n\n## Setup\n\n1. ...\n2. ...\n3. ...\n\n## Gameplay\n\n### Phase 1: <name> (<AUTO|SINGLE_PLAYER|ALL_PLAYERS>)\n\n- Entry:\n- Allowed actions:\n- Validation:\n- Completion -> Next phase:\n\n### Phase 2: <name>\n\n- ...\n\n## Scoring and Progression\n\n- ...\n\n## Winning Conditions\n\n- End trigger:\n- Winner determination:\n- Tie-breaker:\n```\n\n## Iteration Workflow\n\n1. Edit `rule.md` first.\n2. Align `manifest.json` to the updated rules.\n3. Run `dreamboard update`.\n4. Implement or refine `app/phases/*.ts`.\n5. Validate the flow with `dreamboard run`, `dreamboard test generate`, and `dreamboard test run` as needed.\n',
1943
+ "references/rule-authoring.md": '# Rule Authoring Guide\n\nUse this guide when creating or rewriting `rule.md`.\n\n`rule.md` should explain how the game works in plain language. It is the source of intent for the game. `manifest.json` comes later and turns that intent into runtime structure.\n\nKeep the jobs separate:\n\n- `rule.md` explains the rules\n- `manifest.json` defines the runtime model\n- `app/phases/*.ts` implements the rules\n\nIf you need schema details, use [manifest-authoring.md](manifest-authoring.md). If you need help deciding between hands and decks, use [hands-vs-decks.md](hands-vs-decks.md).\n\n## Recommended Document Structure\n\nUse this section order in `rule.md`.\n\n1. **Overview**\n\n - Game premise and objective\n - Player count\n - Match length target\n\n2. **Components**\n\n - Cards, hands, decks, tokens, dice, boards, resources\n - Public vs hidden information\n - Limits such as hand size or token caps\n\n3. **Setup**\n\n - Starting layout\n - Starting player and turn order\n - Initial cards, resources, and other player state\n\n4. **Gameplay**\n\n - Turn or phase loop in order\n - Available actions in each phase\n - Validation constraints\n - Transition conditions between phases\n\n5. **Scoring and Progression**\n\n - How points or progress are gained or lost\n - When scoring happens\n - Tie-breakers\n\n6. **Winning Conditions**\n\n - Exact end-game triggers\n - Winner resolution rules\n\n7. **Special Rules and Edge Cases**\n\n - Simultaneous actions\n - Empty deck behavior\n - Invalid or no-op actions\n - What happens when a player cannot act\n\n## Writing Rules That Map Cleanly To The Engine\n\n- **Phase model**\n State whether a phase is automatic, single-player, or all-players, and say how it ends.\n- **Action model**\n Name each action, say what it does, and list required inputs and constraints.\n- **State model**\n Separate stored state from derived state. If something can be computed from cards, resources, or the current phase, call that out.\n- **Visibility model**\n Say who can see what, when hidden information becomes public, and when reveals happen.\n- **Determinism**\n Resolve ambiguous cases with explicit priority order. Define random events precisely.\n\n## Writing Style\n\n- Use `must` for mandatory behavior.\n- Use `may` only for optional player choices.\n- Define terms once, then reuse the same words consistently.\n- Avoid UI-specific verbs such as "click" or "drag".\n\n## High-Signal Template\n\nUse this template when creating or rewriting `rule.md`:\n\n```markdown\n# <Game Name>\n\n## Overview\n\n- Players: <min-max> (optimal: <n>)\n- Objective: <how to win>\n- Duration: <target minutes>\n\n## Components\n\n- Card sets:\n- Shared decks:\n- Player hands:\n- Boards:\n- Resources, tokens, or dice:\n- Public vs hidden information:\n\n## Setup\n\n1. ...\n2. ...\n3. ...\n\n## Gameplay\n\n### Phase 1: <name> (<AUTO|SINGLE_PLAYER|ALL_PLAYERS>)\n\n- Entry:\n- Allowed actions:\n- Validation:\n- Completion -> Next phase:\n\n### Phase 2: <name>\n\n- ...\n\n## Scoring and Progression\n\n- ...\n\n## Winning Conditions\n\n- End trigger:\n- Winner determination:\n- Tie-breaker:\n```\n\n## Iteration Workflow\n\n1. Edit `rule.md` first.\n2. Align `manifest.json` to the updated rules.\n3. Run `dreamboard sync`.\n4. Implement or refine `app/phases/*.ts`.\n5. Validate the flow with `dreamboard run`, `dreamboard test generate`, and `dreamboard test run` as needed.\n',
1944
1944
  "references/scenario-format.md": '# Scenario Format\n\nScenarios are JSON files used with `dreamboard run --scenario <file>` to automate gameplay actions.\n\nUse this file for the JSON runtime-scenario contract.\n\n- For TypeScript regression tests with `dreamboard test generate` / `dreamboard test run`, see [test-harness.md](test-harness.md).\n- For what adversarial cases to write with these JSON scenarios, see [adversarial-testing.md](adversarial-testing.md).\n\n`dreamboard run` is now a single command flow:\n\n1. Without `--scenario`, it observes SSE until a stop condition (default `YOUR_TURN`).\n2. With `--scenario`, it executes scenario steps (default `--max-steps 1`).\n3. Scenario driver defaults to `api` (no Playwright required).\n4. Playwright is used only for `--scenario-driver ui` or `--screenshot`.\n\n## Default Structure (API Driver)\n\n```json\n{\n "steps": [\n {\n "playerId": "player-1",\n "actionType": "playCard",\n "parameters": { "cardId": "hearts-7" },\n "turns": 1\n },\n {\n "playerId": "player-1",\n "actionType": "endTurn",\n "parameters": {},\n "turns": 1\n }\n ]\n}\n```\n\n## API Step Fields (Default)\n\n| Field | Type | Description |\n| ------------ | ------------------------- | ----------------------------------------------------------------- |\n| `playerId` | `string` | Player ID this step applies to (required) |\n| `actionType` | `string` | Action type submitted to runtime API (required) |\n| `parameters` | `Record<string, unknown>` | Action parameters object (optional, default `{}`) |\n| `turns` | `number` | Number of turns to wait after this action (optional, default `1`) |\n\n## UI Step Fields (`--scenario-driver ui`)\n\n| Field | Type | Description |\n| ---------- | ---------- | --------------------------------------------------------------------------- |\n| `playerId` | `string` | Player ID this step applies to (required) |\n| `buttons` | `string[]` | Mouse/keyboard buttons to simulate (`"left_mouse_button"`, `"right"`, etc.) |\n| `turns` | `number` | Number of turns to apply this step |\n| `mouse_x` | `number` | X coordinate of the simulated click (optional) |\n| `mouse_y` | `number` | Y coordinate of the simulated click (optional) |\n\n## Conventions\n\n1. `playerId` is required on every step.\n2. Run one acting step per invocation for deterministic agent loops:\n - `dreamboard run` (observe turn context)\n - `dreamboard run --scenario ... --max-steps 1` (act once)\n3. All steps executed in one invocation must target the same `playerId`.\n4. The step `playerId` must be eligible for the latest `YOUR_TURN` context.\n\n## Observe Artifacts\n\nWhen running without `--scenario`, the CLI writes:\n\n1. `.dreamboard/run/session.json`\n2. `.dreamboard/run/events.ndjson`\n3. `.dreamboard/run/latest-your-turn.json`\n4. `.dreamboard/run/last-run-summary.json`\n\nBy default, `events.ndjson` stores only `YOUR_TURN` messages.\n\n## Usage\n\n```bash\n# Observe until actionable state (default: until YOUR_TURN)\ndreamboard run\n\n# Observe stream behavior\ndreamboard run --until GAME_ENDED\ndreamboard run --observe-events all\ndreamboard run --timeout-ms 15000\ndreamboard run --screenshot\n\n# Execute API scenario steps (default driver, no Playwright required)\ndreamboard run --scenario path/to/scenario.json\ndreamboard run --scenario path/to/scenario.json --max-steps 1\n\n# Execute UI scenario steps (Playwright driver)\ndreamboard run --scenario path/to/scenario.json --scenario-driver ui --max-steps 1\ndreamboard run --scenario path/to/scenario.json --scenario-driver ui --max-steps 1 --screenshot\n\n# Start a fresh session instead of resuming\ndreamboard run --new-session --players 4\n```\n',
1945
1945
  "references/test-harness.md": '# Test Harness\n\nTypeScript scenario tests run against the Dreamboard backend API using deterministic base snapshots.\n\nUse this file for the TypeScript regression harness:\n\n- `dreamboard test generate`\n- `dreamboard test run`\n\nThis is not the same system as `dreamboard run --scenario <file>.json`.\n\n- For JSON runtime scenarios, see [scenario-format.md](scenario-format.md).\n- For rejection-focused runtime coverage, see [adversarial-testing.md](adversarial-testing.md).\n\n## Workspace Layout\n\nThe harness expects this structure in the game project:\n\n- `test/base-scenarios.json`\n- `test/scenarios/*.scenario.ts`\n- `test/generated/*` (generated by `dreamboard test generate`)\n\nCurrent canonical example workspace (`examples/things-in-rings`) includes bases like `initial-turn` and `after-first-placement`, plus scenarios under `test/scenarios/`.\n\n## Commands\n\nGenerate deterministic bases:\n\n```bash\ndreamboard test generate\n```\n\nRun all scenarios:\n\n```bash\ndreamboard test run\n```\n\nRun one scenario file:\n\n```bash\ndreamboard test run --scenario test/scenarios/reject-card-not-in-hand.scenario.ts\n```\n\nSource-checkout builds may expose internal environment overrides for harness work, but those flags are not part of the published CLI contract.\n\nRegenerate whenever any fingerprint input changes: base definition (`seed`, `players`, `steps`), game manifest hash, compiled result ID, or game ID.\n\n## Base Definitions\n\n`test/base-scenarios.json` is required and must explicitly include `initial-turn`.\n\nEach base entry has:\n\n- `seed: number`\n- `players: number`\n- `steps: ApiScenarioStep[]`\n\n`steps` may be empty. Use an empty-step base plus an empty-step scenario to assert opening-state invariants without inventing fake actions.\n\nExample:\n\n```json\n{\n "initial-turn": {\n "seed": 1337,\n "players": 4,\n "steps": []\n },\n "after-first-placement": {\n "seed": 1337,\n "players": 4,\n "steps": [\n {\n "playerId": "player-2",\n "actionType": "placeThing",\n "parameters": { "cardId": "a-shadow", "ringId": "ring-1" }\n }\n ]\n }\n}\n```\n\nGeneration flow for each used base:\n\n1. Create session with deterministic seed/player count.\n2. Start game.\n3. Wait for first `YOUR_TURN`.\n4. Execute base setup steps.\n5. Save snapshot + fingerprint metadata.\n\nBase setup events are not counted in test assertions. `events` in `then.assert` only reflects actions from `when.steps`, so zero-step scenarios start with an empty event history.\n\n## Generated Artifacts\n\n`dreamboard test generate` writes:\n\n- `test/generated/base-states.generated.ts`\n- `test/generated/base-states.generated.d.ts`\n- `test/generated/scenario-manifest.generated.ts`\n- `test/generated/.generation-meta.json`\n\nThese files are generated and should not be edited manually.\n\n## Scenario Contract\n\nScenarios are loaded from `test/scenarios/**/*.scenario.ts` and validated strictly (unknown fields are rejected).\n\nIn current projects, scenario files typically import from `../testing-types`:\n\n```ts\nimport { AssertContext, defineScenario } from "../testing-types";\n\nexport default defineScenario({\n meta: {\n id: "reject-card-not-in-hand",\n description: "Player tries to place a card they don\'t hold",\n },\n given: {\n base: "initial-turn",\n },\n when: {\n steps: [\n {\n playerId: "player-2",\n actionType: "placeThing",\n parameters: { cardId: "a-dog", ringId: "ring-1" },\n },\n ],\n },\n then: {\n assert: ({ gameState, events, expect }: AssertContext) => {\n expect(events.count("ACTION_REJECTED")).toBe(1);\n expect(events.last("ACTION_REJECTED")?.errorCode).toBe(\n "CARD_NOT_IN_HAND",\n );\n expect(gameState.currentState).toBe("placeThing");\n },\n },\n});\n```\n\n`given.base` must exist in `test/base-scenarios.json`, or the run fails with an available-base list.\n\nFor invariant coverage, add at least one zero-step scenario:\n\n```ts\nimport {\n AssertContext,\n defineScenario,\n getNormalizedHands,\n} from "../testing-types";\n\nexport default defineScenario({\n meta: {\n id: "initial-state",\n description: "Opening hands are dealt to the correct players",\n },\n given: {\n base: "initial-turn",\n },\n when: {\n steps: [],\n },\n then: {\n assert: ({ gameState, expect }: AssertContext) => {\n const hands = getNormalizedHands(gameState);\n expect(hands["player-1"]?.hand).toEqual([\n "seat-1-flower-1",\n "seat-1-flower-2",\n "seat-1-flower-3",\n "seat-1-skull",\n ]);\n },\n },\n});\n```\n\n## Runtime Assertion Surface\n\n`then.assert` receives `{ gameState, events, expect }`.\n\n- `events.count(type)` returns number of matching events.\n- `events.last(type)` returns the latest matching event.\n- `events.all(type)` returns all matching events.\n- `expect(actual)` supports `toBe`, `toEqual`, `toBeDefined`, `toContain`, and `toBeGreaterThanOrEqual`.\n\nDuring scenario assertions, event history includes only scenario steps (base-step events are cleared before scenario execution).\n\n`gameState.globalVariables` and `gameState.playerVariables` are JSON-parsed for assertions.\n\n`testing-types` also exports:\n\n- `getNormalizedHands(gameState)` to read each player hand with sorted card IDs.\n- `getNormalizedDecks(gameState)` to read deck contents with sorted card IDs.\n\n## Failure And Staleness Signals\n\nCommon failures indicate stale generated bases:\n\n- `Missing test/generated/base-states.generated.ts. Run \'dreamboard test generate\' first.`\n- `Fingerprint mismatch: base setup changed (...). Run \'dreamboard test generate\' to refresh deterministic bases.`\n- `Fingerprint mismatch: compiled result changed (...). Run \'dreamboard test generate\' to refresh deterministic bases.`\n- `Fingerprint mismatch: base setup changed (...); compiled result changed (...). Run \'dreamboard test generate\' to refresh deterministic bases.`\n- Unknown `given.base` values.\n\n## Common edge cases\n\nWhen testing out-of-turn behavior in any turn-based game, include these checks:\n\n- Wrong actor attempts a valid action: choose a base where the active player is known, send a normal action from a different player, and assert a turn-violation error (for example `NOT_YOUR_TURN`).\n- Unauthorized role attempts an action: if your game has role-restricted actions, attempt the action from an actor without permission and assert the appropriate authorization error.\n- Correct actor, wrong phase/state: attempt an action that is valid in one phase/state while the game is in another, and assert a deterministic phase/state rejection.\n- Consecutive out-of-turn attempts: send multiple invalid actions from different non-active players and verify each is rejected independently.\n- No state mutation on rejection: assert no successful action event was emitted, the game phase/state is unchanged, and the active player pointer is unchanged.\n\nRecommended assertion template:\n\n```ts\nexpect(events.count("ACTION_REJECTED")).toBe(1);\nexpect(events.count("ACTION_EXECUTED")).toBe(0);\nexpect(events.last("ACTION_REJECTED")?.errorCode).toBe("NOT_YOUR_TURN");\nexpect(gameState.currentState).toBe(expectedState);\nexpect(gameState.currentPlayerIds[0]).toBe(expectedActivePlayerId);\n```\n',
1946
1946
  "references/ui-best-practices.md": '# UI Best Practices\n\nUse this guide when implementing `ui/App.tsx` and `ui/components/*`.\n\nThis page is about rendering, interaction, and UI data flow. Keep rules in `rule.md`, runtime structure in `manifest.json`, and phase-specific UI data in `getUIArgs()`.\n\n## Core Principles\n\n1. **Start with SDK components**\n Prefer `./sdk/components/` and customize them with render props before building custom replacements.\n2. **Design mobile-first**\n Mobile is the baseline. Desktop can add polish, but the primary layout should work on smaller screens first.\n3. **Do not duplicate authoritative game state**\n Use hooks such as `useGameState`, `useUIArgs`, and `useAction` instead of mirroring shared state in local React state.\n4. **Wrap actions in error handling**\n Show feedback through `useToast` or another clear error surface.\n5. **Keep types intact**\n Use the generated hook and action types. Avoid `any`.\n6. **Reserve a persistent feedback area**\n Players should always be able to see the current phase, turn ownership, latest meaningful result, and end-of-game summary.\n\n## SDK Hooks\n\nUse these from `./sdk/hooks/`.\n\n| Hook | Returns | When to use |\n| ------------------------ | ------------------------------------------------------------------------- | ---------------------------------- |\n| `useGameState` | Full game state (`currentPlayerIds`, `decks`, `currentState`, `isMyTurn`) | Core session state |\n| `useCard(cardId)` | `CardItem \\| undefined` | Resolve one card |\n| `useCards(cardIds)` | `CardItem[]` | Resolve multiple cards |\n| `useMyHand(handId)` | Current player\'s cards for one hand | Private or public per-player cards |\n| `useMyResources()` | Current player\'s resources | Resource display |\n| `useMe()` | `{ playerId, name, isHost }` | Current player identity |\n| `usePlayerInfo()` | All players keyed by ID | Player labels and scoreboards |\n| `useAction()` | Phase-specific action submitters | Submitting actions |\n| `useUIArgs()` | Phase-specific UI args | Data returned from `getUIArgs()` |\n| `useGameNotifications()` | Event listeners | Turn prompts, rejections, game end |\n| `useLobby()` | Lobby state | Seating and pre-game UI |\n| `useDice(dieIds)` | Dice values and roll function | Dice mechanics |\n\n## SDK Components\n\nUse these from `./sdk/components/`.\n\n### Core display\n\n| Component | Purpose |\n| --------------- | ---------------------------------------------- |\n| `Card` | Display a card with animation support |\n| `ConnectedCard` | Resolve card data from context automatically |\n| `Hand` | Render a player\'s hand with custom card layout |\n| `PlayArea` | Render a shared card area |\n| `PlayerInfo` | Show player name, score, and turn state |\n\n### Actions and status\n\n| Component | Purpose |\n| ----------------- | ---------------------------------------- |\n| `ActionButton` | Action button with optional cost display |\n| `ActionPanel` | Container for related actions |\n| `ActionGroup` | Group actions inside a panel |\n| `PhaseIndicator` | Show current phase and turn state |\n| `ResourceCounter` | Display typed resources |\n| `CostDisplay` | Show affordability |\n| `DiceRoller` | Show die values |\n| `GameEndDisplay` | Show final results |\n| `Drawer` | Mobile overflow surface |\n\n### Board components\n\n| Component | Use case | Hook |\n| -------------- | ----------------------------- | --------------------------------- |\n| `SquareGrid` | Chess, checkers, grid tactics | `useSquareBoard(boardId)` |\n| `HexGrid` | Hex maps and wargames | `useHexBoard(boardId)` |\n| `TrackBoard` | Race tracks and score tracks | `useTrackBoard(boardId)` |\n| `NetworkGraph` | Connected-node boards | `useNetworkBoard(boardId)` |\n| `ZoneMap` | Area-control boards | `useZoneMap(zones, pieces)` |\n| `SlotSystem` | Worker placement | `useSlotSystem(slots, occupants)` |\n\n## Layout Guidelines\n\n- Use Tailwind CSS for styling.\n- Use `clsx` for conditional class composition.\n- Use `framer-motion` for meaningful motion, not motion everywhere.\n- Keep touch targets large and spacing readable on mobile.\n- Use `useIsMobile()` when behavior must differ by device.\n\n## Persistent Feedback Region\n\nReserve a visible area near the top of the layout for status that should survive subview changes.\n\nThat region should be able to show:\n\n- the current phase\n- whether it is the local player\'s turn\n- the latest meaningful status message\n- round or trick resolution results\n- game-over state and winner summary\n\nIf your game uses `AUTO` phases, do not let those transitions erase the only copy of round-resolution feedback before the next interactive view appears.\n\n## Common Patterns\n\n### Phase-based rendering\n\n```tsx\nconst { currentState } = useGameState();\nconst uiArgs = useUIArgs();\nconst actions = useAction();\n\nif (currentState === "playCards") {\n const phaseArgs = uiArgs.playCards;\n const phaseActions = actions.playCardsActions;\n return <PlayCardsView args={phaseArgs} actions={phaseActions} />;\n}\n```\n\n### Action submission with error handling\n\n```tsx\nconst { toast } = useToast();\n\nconst handlePlay = async () => {\n try {\n await actions.playCardsActions.playCard({ cardIds: selectedCards });\n } catch (err) {\n toast.error(err instanceof Error ? err.message : "Action failed");\n }\n};\n```\n\n### Public hands in UI\n\nFor per-player containers that should be visible to everyone, prefer the built-in public-hand hook instead of duplicating that data in `getUIArgs()`.\n\n```ts\nimport { useMyHand, usePublicHands } from "@dreamboard/ui-sdk";\n\nconst myHand = useMyHand("hand");\nconst scoredByPlayer = usePublicHands("scored-area");\nconst player2Scored = scoredByPlayer["player-2"] ?? [];\n```\n\nNotes:\n\n- `useMyHand(handId)` reads the current player\'s copy of that hand\n- `usePublicHands(handId)` reads all players\' copies, keyed by `playerId`\n- `usePublicHands()` only works for hands marked `visibility: "public"`\n\n## Genre-Specific Guides\n\nUse these when the game matches one of the built-in patterns:\n\n- [ui-genre-trick-taking.md](ui-genre-trick-taking.md)\n- [ui-genre-worker-placement.md](ui-genre-worker-placement.md)\n- [ui-genre-resource-management.md](ui-genre-resource-management.md)\n',
@@ -1949,7 +1949,7 @@ var SKILL_ASSET_FILES = {
1949
1949
  "references/ui-genre-worker-placement.md": '# UI Guide: Worker Placement Games\n\nPatterns for Agricola, Viticulture, Lords of Waterdeep, Caverna, and similar euro-style worker placement games.\n\n## Game characteristics\n\n- Players place **worker tokens** on shared **action slots** to gain resources or take actions.\n- Slots may be exclusive (one player per round) or shared.\n- Phases typically include: placement, resolution/harvest, and cleanup.\n- Key UI signals: available slots, slot costs/rewards, number of workers remaining, resource counts.\n\n## Recommended SDK components\n\n### Primary\n\n| Component | Usage |\n| --------------------- | ----------------------------------------------------------------------------------------------------- |\n| `SlotSystem` | Core board \u2014 renders all action slots with occupants. Use `layout="grouped"` to organize by category. |\n| `DefaultSlotItem` | Pre-built slot renderer showing name, description, capacity, cost/reward labels. |\n| `DefaultSlotOccupant` | Pre-built worker token display with player color. |\n| `ResourceCounter` | Display each player\'s resource pool (wood, stone, food, etc.). |\n| `ActionButton` | "Place Worker" button with integrated cost display and affordability check. |\n\n### Supporting\n\n| Component | Usage |\n| ----------------------------- | ------------------------------------------------------------ |\n| `ActionPanel` / `ActionGroup` | Group available actions (e.g., "Place Worker", "End Turn"). |\n| `CostDisplay` | Show resource costs on slot details. |\n| `PlayerInfo` | Show each player\'s name, workers remaining, and turn status. |\n| `PhaseIndicator` | Distinguish placement phase from harvest/scoring phases. |\n| `GameEndDisplay` | Final scoring with detailed breakdown. |\n| `Drawer` | Mobile drawer for detailed slot inspection. |\n\n### Hooks\n\n| Hook | Usage |\n| --------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `useSlotSystem(slots, occupants)` | Utility for slot lookups: `getSlot`, `isFull`, `getRemainingCapacity`, `getOccupantsByPlayer`. |\n| `useMyResources()` | Current player\'s resource pool. |\n| `useGameState` | Get `isMyTurn`, `currentState`, `currentPlayerIds`. |\n| `useAction` | Submit place-worker or end-turn actions. |\n| `useUIArgs` | Get phase-specific data (available slots, worker count, etc.). |\n| `usePlayerInfo` | Map player IDs to names and colors. |\n\n## Key patterns\n\n### Slot board with placement\n\n```tsx\nconst { placementPhase } = useUIArgs();\nconst slots = placementPhase?.slots ?? [];\nconst occupants = placementPhase?.occupants ?? [];\nconst availableSlotIds = new Set(placementPhase?.availableSlotIds ?? []);\nconst slotApi = useSlotSystem(slots, occupants);\n\n<SlotSystem\n slots={slots}\n occupants={occupants}\n layout="grouped"\n renderSlot={(slot, slotOccupants) => (\n <DefaultSlotItem\n name={slot.name}\n description={slot.description}\n capacity={slot.capacity}\n occupantCount={slotOccupants.length}\n isExclusive={slot.exclusive}\n isAvailable={availableSlotIds.has(slot.id)}\n costLabel={formatCost(slot.cost)}\n rewardLabel={formatReward(slot.reward)}\n onClick={() => handlePlaceWorker(slot.id)}\n renderOccupants={() => (\n <div className="flex gap-1">\n {slotOccupants.map((o) => (\n <DefaultSlotOccupant\n key={o.pieceId}\n color={playerColors[o.playerId]}\n label={playerInfo[o.playerId]?.name}\n />\n ))}\n </div>\n )}\n />\n )}\n/>;\n```\n\n### Resource display\n\n```tsx\nconst resources = useMyResources();\n\n<ResourceCounter\n resources={[\n {\n type: "wood",\n label: "Wood",\n icon: TreePine,\n iconColor: "text-amber-700",\n },\n {\n type: "stone",\n label: "Stone",\n icon: Mountain,\n iconColor: "text-slate-400",\n },\n { type: "food", label: "Food", icon: Apple, iconColor: "text-red-400" },\n { type: "gold", label: "Gold", icon: Coins, iconColor: "text-yellow-400" },\n ]}\n counts={resources}\n layout="row"\n/>;\n```\n\n### Action buttons with costs\n\n```tsx\n<ActionButton\n label="Build Farm"\n cost={{ wood: 2, stone: 1 }}\n currentResources={resources}\n resourceDefs={resourceDefinitions}\n icon={Home}\n onClick={() => actions.buildActions.build({ buildingType: "farm" })}\n/>\n```\n\n## UIArgs recommendations\n\n```typescript\n// shared/ui-args.ts\nexport interface PlacementPhaseUIArgs {\n slots: SlotDefinition[];\n occupants: SlotOccupant[];\n availableSlotIds: string[]; // Slots the current player can legally place on\n workersRemaining: number;\n}\n\nexport interface HarvestPhaseUIArgs {\n feedingRequired: number; // Food needed\n currentFood: number;\n}\n```\n',
1950
1950
  "references/ui-style-guide.md": '# UI Style Guide\n\nThis style guide details the design philosophy, visual tokens, and core components for the Dreamboard.games UI SDK. The framework uses a **Hand-Drawn** aesthetic that emphasizes human creativity over corporate polish.\n\n## Design Philosophy\n\nThe Hand-Drawn design style celebrates authentic imperfection and human touch in a digital world. It rejects clinical precision in favor of organic, playful irregularity that evokes sketches on paper, sticky notes on a wall, and napkin diagrams from a brainstorming session.\n\n### Core Principles\n\n- **No Straight Lines**: Every border, shape, and container uses irregular border-radius values to create wobbly, hand-drawn edges that reject geometric perfection.\n- **Authentic Texture**: The design layer paper grain, dot patterns, and subtle background textures to simulate physical media (notebook paper, post-its, sketch pads).\n- **Playful Rotation**: Elements are deliberately tilted using small rotation transforms (`-rotate-1`, `rotate-2`) to break rigid grid alignment and create casual energy.\n- **Hard Offset Shadows**: Reject soft blur shadows entirely. Use solid, offset box-shadows (like `4px 4px 0px`) to create a cut-paper, layered collage aesthetic.\n- **Handwritten Typography**: Use exclusively handwritten or marker-style fonts (like Kalam for headings and Patrick Hand for body text) that feel human and approachable.\n- **Limited Color Palette**: Stick to pencil blacks (`#2d2d2d`), paper whites (`#fdfbf7`), correction marker red (`#ff4d4d`), post-it yellow (`#fff9c4`), and ballpoint blue (`#2d5da1`).\n- **Intentional Messiness**: Embrace overlap, asymmetry, and visual "mistakes" that make the design feel spontaneous.\n\n## CSS Tokens & Utilities\n\nThe platform provides several global CSS utilities designed for this aesthetic:\n\n- **Borders**: `.wobbly-border`, `.wobbly-border-md`, `.wobbly-border-lg` apply the irregular `border-radius` shapes. Used with `border-2`, `border-[3px]`, or `border-[4px]`.\n- **Shadows**: `.hard-shadow`, `.hard-shadow-md`, `.hard-shadow-lg` apply the solid offset shadows. Use `.hard-shadow-sm` for active states (buttons pressing flat).\n\n## Implementing in Components\n\nWhen writing or modifying UI components in `ui/` or `ui-sdk/`:\n\n### Containers & Cards\n\n- Add `wobbly-border-md` or `wobbly-border-lg`.\n- Use `border-[3px] border-border` to give it a thick pencil-like stroke.\n- Apply `bg-[#fdfbf7]` (warm paper) or `bg-white`.\n- Add a solid shadow with `hard-shadow` or `hard-shadow-lg`.\n- Slightly rotate the element with `rotate-1` or `-rotate-1`.\n\n### Buttons\n\n- Use `wobbly-border`.\n- Normal state: `border-[3px] border-border bg-[#fdfbf7] text-foreground hard-shadow`.\n- Hover state: `hover:bg-primary hover:text-white hover:hard-shadow-sm hover:-translate-y-1`.\n- Active state: `active:shadow-none active:translate-y-1 active:translate-x-1`.\n- Secondary variants can use `bg-[#e5e0d8]` or `bg-[#fff9c4]`.\n\n### Typography\n\n- Add `font-sans` for standard handwritten text or `font-display` for bold marker headings.\n- Make things bold! `font-bold` works well with handwritten fonts to make them legible.\n\n### Decoration\n\n- For emphasis, wrap text in a post-it styling: `bg-[#fff9c4] px-2 py-1 border-2 border-border wobbly-border rotate-2 inline-block`.\n- Add "tape" to tops of containers: `<div className="absolute top-2 left-1/2 -translate-x-1/2 w-24 h-6 bg-[#e5e0d8] border border-border opacity-80 backdrop-blur-sm -rotate-2 z-10" />`.\n',
1951
1951
  "scripts/events-extract.mjs": '#!/usr/bin/env node\n\nimport { access, readFile } from "node:fs/promises";\n\nconst DEFAULT_EVENTS_PATH = ".dreamboard/run/events.ndjson";\n\nfunction printHelp() {\n console.log(`Extract fields from dreamboard run artifacts.\n\nUsage:\n node .agents/skills/dreamboard/scripts/events-extract.mjs [options]\n\nOptions:\n --file, -f <path> NDJSON file path (default: ${DEFAULT_EVENTS_PATH})\n --type <eventType> Filter by event type (repeatable)\n --field <path> Dot path to extract (e.g. message.reason)\n --player <playerId> Filter by player ID across common message fields\n --limit <number> Limit output records\n --no-index Omit line index in output\n --help, -h Show this help\n\nExamples:\n node .agents/skills/dreamboard/scripts/events-extract.mjs --type YOUR_TURN\n node .agents/skills/dreamboard/scripts/events-extract.mjs --type ACTION_REJECTED --field message.reason\n node .agents/skills/dreamboard/scripts/events-extract.mjs --player player-2 --field message.availableActions\n`);\n}\n\nfunction parseCliArgs(argv) {\n const options = {\n filePath: DEFAULT_EVENTS_PATH,\n types: new Set(),\n includeIndex: true,\n };\n\n for (let index = 0; index < argv.length; index += 1) {\n const token = argv[index];\n const nextToken = argv[index + 1];\n\n if (token === "--help" || token === "-h") {\n printHelp();\n process.exit(0);\n }\n if (token === "--no-index") {\n options.includeIndex = false;\n continue;\n }\n if (token === "--file" || token === "-f") {\n if (!nextToken) {\n throw new Error("--file requires a value");\n }\n options.filePath = nextToken;\n index += 1;\n continue;\n }\n if (token === "--type") {\n if (!nextToken) {\n throw new Error("--type requires a value");\n }\n options.types.add(nextToken);\n index += 1;\n continue;\n }\n if (token === "--field") {\n if (!nextToken) {\n throw new Error("--field requires a value");\n }\n options.fieldPath = nextToken;\n index += 1;\n continue;\n }\n if (token === "--player") {\n if (!nextToken) {\n throw new Error("--player requires a value");\n }\n options.playerId = nextToken;\n index += 1;\n continue;\n }\n if (token === "--limit") {\n if (!nextToken) {\n throw new Error("--limit requires a value");\n }\n const parsed = Number.parseInt(nextToken, 10);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new Error("--limit must be a positive integer");\n }\n options.limit = parsed;\n index += 1;\n continue;\n }\n\n throw new Error(`Unknown argument: ${token}`);\n }\n\n return options;\n}\n\nfunction getByPath(value, path) {\n const segments = path.split(".").filter(Boolean);\n let current = value;\n\n for (const segment of segments) {\n if (current === null || current === undefined) {\n return undefined;\n }\n if (typeof current !== "object") {\n return undefined;\n }\n current = current[segment];\n }\n\n return current;\n}\n\nfunction extractPlayerCandidates(record) {\n const message = record.message ?? {};\n const players = new Set();\n\n const singleCandidates = [\n message.playerId,\n message.targetPlayer,\n message.toUser,\n ];\n for (const candidate of singleCandidates) {\n if (typeof candidate === "string") {\n players.add(candidate);\n }\n }\n\n const listCandidates = [\n message.activePlayers,\n message.previousPlayers,\n message.currentPlayers,\n message.controllablePlayerIds,\n message.eligiblePlayerIds,\n ];\n for (const candidate of listCandidates) {\n if (!Array.isArray(candidate)) {\n continue;\n }\n for (const item of candidate) {\n if (typeof item === "string") {\n players.add(item);\n }\n }\n }\n\n return [...players];\n}\n\nfunction toOutputRecord(record, lineIndex, options) {\n const output = {};\n\n if (options.includeIndex) {\n output.index = lineIndex;\n }\n output.observedAt = record.observedAt ?? null;\n output.sessionId = record.sessionId ?? null;\n output.eventId = record.eventId ?? null;\n output.type = record.type ?? null;\n\n if (options.fieldPath) {\n output.field = options.fieldPath;\n output.value = getByPath(record, options.fieldPath);\n } else {\n output.message = record.message ?? null;\n }\n\n return output;\n}\n\nasync function main() {\n const options = parseCliArgs(process.argv.slice(2));\n await access(options.filePath);\n\n const text = await readFile(options.filePath, "utf8");\n const lines = text\n .split(/\\r?\\n/u)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n\n let emitted = 0;\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const line = lines[lineIndex];\n let record;\n try {\n record = JSON.parse(line);\n } catch {\n continue;\n }\n\n if (options.types.size > 0 && !options.types.has(record.type ?? "")) {\n continue;\n }\n\n if (options.playerId) {\n const players = extractPlayerCandidates(record);\n if (!players.includes(options.playerId)) {\n continue;\n }\n }\n\n const output = toOutputRecord(record, lineIndex + 1, options);\n console.log(JSON.stringify(output));\n emitted += 1;\n\n if (options.limit !== undefined && emitted >= options.limit) {\n break;\n }\n }\n}\n\nmain().catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`events-extract failed: ${message}`);\n process.exit(1);\n});\n',
1952
- "SKILL.md": "---\nname: dreamboard\ndescription: Create multiplayer, rule-enforced, turn-based board game on Dreamboard.games platform.\nmetadata:\n short-description: Dreamboard Game Development Workflow\n tags: [dreamboard, cli, game-dev, board-game, turn-based, multiplayer]\n---\n\n# Dreamboard\n\n## Goal\n\nCreate and iterate on a Dreamboard game locally, then sync rules, scaffold, UI, and tests back to the platform.\n\n## Why Dreamboard\n\nDreamboard exists to turn a board-game idea into a playable digital prototype quickly.\n\n- Describe the game instead of hand-building every primitive first.\n- Generate rules scaffolding, components, and UI structure faster.\n- Playtest with real players without repeated print-and-play friction.\n- Iterate live while Dreamboard handles turns, hands, and multiplayer plumbing.\n\n## Prereqs\n\n- Dreamboard CLI installed and available as `dreamboard`\n Install with `npm install -g dreamboard`\n- Authenticated via `dreamboard login`\n\n## Building Your First Game\n\nRead [references/building-your-first-game.md](references/building-your-first-game.md)\n\n## References\n\n- Rules and manifest:\n [references/rule-authoring.md](references/rule-authoring.md),\n [references/manifest-authoring.md](references/manifest-authoring.md)\n- App logic and engine concepts:\n [references/phase-handlers.md](references/phase-handlers.md),\n [references/api-reference.md](references/api-reference.md),\n [references/hands-vs-decks.md](references/hands-vs-decks.md),\n [references/all-players-tracking.md](references/all-players-tracking.md),\n [references/app-best-practices.md](references/app-best-practices.md)\n- UI and UX:\n [references/ui-best-practices.md](references/ui-best-practices.md),\n [references/ui-style-guide.md](references/ui-style-guide.md),\n genre references under `references/ui-genre-*.md`\n- TypeScript regression harness (`dreamboard test generate/run`):\n [references/test-harness.md](references/test-harness.md)\n- JSON runtime scenarios (`dreamboard run --scenario ...`):\n [references/scenario-format.md](references/scenario-format.md)\n- Rejection and edge-case pressure using JSON runtime scenarios:\n [references/adversarial-testing.md](references/adversarial-testing.md)\n\n## Testing Modes\n\nThere are two different scenario systems:\n\n- `dreamboard test generate` / `dreamboard test run`\n TypeScript scenario files in `test/scenarios/*.scenario.ts` against deterministic generated base snapshots. Use this for regression coverage.\n- `dreamboard run --scenario path/to/file.json`\n JSON action scripts that drive a live session. Use this for debugging flows, reproductions, and adversarial experiments.\n\n## Guardrails\n\n- `manifest.json` and `rule.md` are the source of truth for scaffolding.\n- Run `dreamboard update` after manifest changes to keep generated files in sync.\n- `dreamboard update` is local-first and fails when remote drift is detected.\n- Use `dreamboard update --pull` only when you explicitly need to reconcile unexpected remote changes into the workspace.\n- `dreamboard test generate` should be re-run after base changes, compiled-result changes, or game identity changes.\n- Add at least one zero-step `initial-state` scenario so setup invariants fail before action tests begin.\n- Keep `scripts/events-extract.mjs` as a debugging helper for `.dreamboard/run/events.ndjson`; assertions belong in test scenarios.\n\n## Editable Surface\n\nEdit:\n\n- `app/phases/*.ts`\n- `shared/ui-args.ts`\n- `ui/App.tsx`\n- `ui/components/*`\n- `ui/sdk/components/*`\n\nDo not edit framework-owned files such as:\n\n- `app/index.ts`\n- `app/sdk/*`\n- `app/generated/*`\n- `ui/index.tsx`\n- `ui/sdk/context/*`\n- `ui/sdk/hooks/*`\n- `ui/sdk/types/*`\n\n## Framework Feedback\n\nUse `feedback.md` in the game project root to record framework issues, missing features, or workflow friction. Include reproduction steps, expected behavior, and actual behavior when possible.\n"
1952
+ "SKILL.md": "---\nname: dreamboard\ndescription: Create multiplayer, rule-enforced, turn-based board game on Dreamboard.games platform.\nmetadata:\n short-description: Dreamboard Game Development Workflow\n tags: [dreamboard, cli, game-dev, board-game, turn-based, multiplayer]\n---\n\n# Dreamboard\n\n## Goal\n\nCreate and iterate on a Dreamboard game locally, then sync rules, scaffold, UI, and tests back to the platform.\n\n## Why Dreamboard\n\nDreamboard exists to turn a board-game idea into a playable digital prototype quickly.\n\n- Describe the game instead of hand-building every primitive first.\n- Generate rules scaffolding, components, and UI structure faster.\n- Playtest with real players without repeated print-and-play friction.\n- Iterate live while Dreamboard handles turns, hands, and multiplayer plumbing.\n\n## Prereqs\n\n- Dreamboard CLI installed and available as `dreamboard`\n Install with `npm install -g dreamboard`\n- Authenticated via `dreamboard login`\n\n## Building Your First Game\n\nRead [references/building-your-first-game.md](references/building-your-first-game.md)\n\n## References\n\n- Rules and manifest:\n [references/rule-authoring.md](references/rule-authoring.md),\n [references/manifest-authoring.md](references/manifest-authoring.md)\n- App logic and engine concepts:\n [references/phase-handlers.md](references/phase-handlers.md),\n [references/api-reference.md](references/api-reference.md),\n [references/hands-vs-decks.md](references/hands-vs-decks.md),\n [references/all-players-tracking.md](references/all-players-tracking.md),\n [references/app-best-practices.md](references/app-best-practices.md)\n- UI and UX:\n [references/ui-best-practices.md](references/ui-best-practices.md),\n [references/ui-style-guide.md](references/ui-style-guide.md),\n genre references under `references/ui-genre-*.md`\n- TypeScript regression harness (`dreamboard test generate/run`):\n [references/test-harness.md](references/test-harness.md)\n- JSON runtime scenarios (`dreamboard run --scenario ...`):\n [references/scenario-format.md](references/scenario-format.md)\n- Rejection and edge-case pressure using JSON runtime scenarios:\n [references/adversarial-testing.md](references/adversarial-testing.md)\n\n## Testing Modes\n\nThere are two different scenario systems:\n\n- `dreamboard test generate` / `dreamboard test run`\n TypeScript scenario files in `test/scenarios/*.scenario.ts` against deterministic generated base snapshots. Use this for regression coverage.\n- `dreamboard run --scenario path/to/file.json`\n JSON action scripts that drive a live session. Use this for debugging flows, reproductions, and adversarial experiments.\n\n## Guardrails\n\n- `manifest.json` and `rule.md` are the source of truth for scaffolding.\n- Run `dreamboard sync` after manifest or rule changes to keep generated files in sync and advance the remote authoring head.\n- Run `dreamboard compile` when you want a new runnable build for the current authored state.\n- Use `dreamboard pull` only when you explicitly need to reconcile unexpected remote authored changes into the workspace.\n- `dreamboard test generate` should be re-run after base changes, compiled-result changes, or game identity changes.\n- Add at least one zero-step `initial-state` scenario so setup invariants fail before action tests begin.\n- Keep `scripts/events-extract.mjs` as a debugging helper for `.dreamboard/run/events.ndjson`; assertions belong in test scenarios.\n\n## Editable Surface\n\nEdit:\n\n- `app/phases/*.ts`\n- `shared/ui-args.ts`\n- `ui/App.tsx`\n- `ui/components/*`\n- `ui/sdk/components/*`\n\nDo not edit framework-owned files such as:\n\n- `app/index.ts`\n- `app/sdk/*`\n- `app/generated/*`\n- `ui/index.tsx`\n- `ui/sdk/context/*`\n- `ui/sdk/hooks/*`\n- `ui/sdk/types/*`\n\n## Framework Feedback\n\nUse `feedback.md` in the game project root to record framework issues, missing features, or workflow friction. Include reproduction steps, expected behavior, and actual behavior when possible.\n"
1953
1953
  };
1954
1954
  var SKILL_MD_CONTENT = SKILL_ASSET_FILES["SKILL.md"];
1955
1955
 
@@ -2247,6 +2247,42 @@ async function getLocalDiff(rootDir) {
2247
2247
  return { modified, added, deleted };
2248
2248
  }
2249
2249
 
2250
+ // src/services/project/project-state.ts
2251
+ function getProjectAuthoringState(projectConfig) {
2252
+ return projectConfig.authoring ?? {};
2253
+ }
2254
+ function getProjectCompileState(projectConfig) {
2255
+ return projectConfig.compile ?? {};
2256
+ }
2257
+ function updateProjectAuthoringState(projectConfig, authoring) {
2258
+ return {
2259
+ ...projectConfig,
2260
+ authoring: {
2261
+ ...getProjectAuthoringState(projectConfig),
2262
+ ...authoring
2263
+ }
2264
+ };
2265
+ }
2266
+ function updateProjectCompileState(projectConfig, compile) {
2267
+ return {
2268
+ ...projectConfig,
2269
+ compile: {
2270
+ ...getProjectCompileState(projectConfig),
2271
+ ...compile
2272
+ }
2273
+ };
2274
+ }
2275
+ function setLatestCompileAttempt(projectConfig, attempt) {
2276
+ return updateProjectCompileState(projectConfig, {
2277
+ ...getProjectCompileState(projectConfig),
2278
+ latestAttempt: attempt,
2279
+ latestSuccessful: attempt.status === "successful" && attempt.resultId ? {
2280
+ resultId: attempt.resultId,
2281
+ authoringStateId: attempt.authoringStateId
2282
+ } : getProjectCompileState(projectConfig).latestSuccessful
2283
+ });
2284
+ }
2285
+
2250
2286
  export {
2251
2287
  consola,
2252
2288
  client,
@@ -2283,6 +2319,10 @@ export {
2283
2319
  loadRule,
2284
2320
  writeSnapshot,
2285
2321
  writeSnapshotFromFiles,
2286
- getLocalDiff
2322
+ getLocalDiff,
2323
+ getProjectAuthoringState,
2324
+ getProjectCompileState,
2325
+ updateProjectAuthoringState,
2326
+ setLatestCompileAttempt
2287
2327
  };
2288
- //# sourceMappingURL=chunk-3I26YUTP.js.map
2328
+ //# sourceMappingURL=chunk-WM7TY7DV.js.map