dreamboard 0.1.24 → 0.1.26

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.
@@ -32,7 +32,7 @@ import {
32
32
  } from "./chunk-4S67A2YA.js";
33
33
  import {
34
34
  createClient
35
- } from "./chunk-H3O43F5P.js";
35
+ } from "./chunk-3JGO32FA.js";
36
36
  import {
37
37
  CLIENT_PROBLEM_TYPES,
38
38
  SERVER_PROBLEM_TYPES,
@@ -72,7 +72,7 @@ import {
72
72
  validatePlayerAction,
73
73
  zGameTopologyManifest,
74
74
  zProblemDetails
75
- } from "./chunk-2RCUHMGL.js";
75
+ } from "./chunk-YI5DUODD.js";
76
76
  import {
77
77
  __export
78
78
  } from "./chunk-D4HDZEJT.js";
@@ -1660,6 +1660,7 @@ function normalizeProjectConfig(config) {
1660
1660
  return {
1661
1661
  gameId: config.gameId,
1662
1662
  slug: config.slug,
1663
+ environment: config.environment,
1663
1664
  authoring: config.authoring,
1664
1665
  compile: config.compile,
1665
1666
  localMaintainerRegistry: config.localMaintainerRegistry,
@@ -1859,6 +1860,191 @@ function getAccessTokenExpiry(accessToken) {
1859
1860
  }
1860
1861
  }
1861
1862
 
1863
+ // src/auth/local-dev-session.ts
1864
+ var DEFAULT_DEV_EMAIL = "test@test.com";
1865
+ var DEFAULT_DEV_PASSWORD = "dreamboard-local-dev-password";
1866
+ var DEFAULT_LOCAL_SERVICE_ROLE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU";
1867
+ async function repairLocalDevSession() {
1868
+ const localConfig = ENVIRONMENT_CONFIGS.local;
1869
+ if (!localConfig?.supabaseUrl || !localConfig.supabaseAnonKey) {
1870
+ return null;
1871
+ }
1872
+ if (!isLocalSupabaseUrl(localConfig.supabaseUrl)) {
1873
+ return null;
1874
+ }
1875
+ const email = valueOrUndefined(process.env.DREAMBOARD_DEV_EMAIL) ?? DEFAULT_DEV_EMAIL;
1876
+ const password = valueOrUndefined(process.env.DREAMBOARD_DEV_PASSWORD) ?? DEFAULT_DEV_PASSWORD;
1877
+ const serviceRoleKey = valueOrUndefined(process.env.SUPABASE_SERVICE_ROLE_KEY) ?? valueOrUndefined(process.env.DREAMBOARD_LOCAL_SUPABASE_SERVICE_ROLE_KEY) ?? DEFAULT_LOCAL_SERVICE_ROLE_KEY;
1878
+ const authBaseUrl = `${localConfig.supabaseUrl.replace(/\/$/, "")}/auth/v1`;
1879
+ const authHeaders = {
1880
+ apikey: localConfig.supabaseAnonKey,
1881
+ "Content-Type": "application/json"
1882
+ };
1883
+ const adminHeaders = {
1884
+ apikey: serviceRoleKey,
1885
+ Authorization: `Bearer ${serviceRoleKey}`,
1886
+ "Content-Type": "application/json"
1887
+ };
1888
+ let session = await signIn({
1889
+ authBaseUrl,
1890
+ headers: authHeaders,
1891
+ email,
1892
+ password
1893
+ });
1894
+ if (!session) {
1895
+ await ensureLocalUser({
1896
+ authBaseUrl,
1897
+ headers: adminHeaders,
1898
+ email,
1899
+ password
1900
+ });
1901
+ session = await signIn({
1902
+ authBaseUrl,
1903
+ headers: authHeaders,
1904
+ email,
1905
+ password
1906
+ });
1907
+ }
1908
+ if (!session?.access_token || !session.refresh_token) {
1909
+ return null;
1910
+ }
1911
+ const credentials = {
1912
+ accessToken: session.access_token,
1913
+ refreshToken: session.refresh_token
1914
+ };
1915
+ await setCredentials(credentials);
1916
+ const globalConfig = await loadGlobalConfig();
1917
+ await saveGlobalConfig({
1918
+ ...globalConfig,
1919
+ environment: "local"
1920
+ });
1921
+ return credentials;
1922
+ }
1923
+ function shouldRepairLocalDevSession(options) {
1924
+ const localConfig = ENVIRONMENT_CONFIGS.local;
1925
+ if (!localConfig) {
1926
+ return false;
1927
+ }
1928
+ return Boolean(options.supabaseUrl && isLocalSupabaseUrl(options.supabaseUrl)) && options.apiBaseUrl === localConfig.apiBaseUrl;
1929
+ }
1930
+ async function signIn(options) {
1931
+ const response = await postAuth(options, "/token?grant_type=password", {
1932
+ email: options.email,
1933
+ password: options.password
1934
+ });
1935
+ return response.ok ? response.data : null;
1936
+ }
1937
+ async function ensureLocalUser(options) {
1938
+ const existing = await findAdminUserByEmail(options);
1939
+ if (existing?.id) {
1940
+ await updateAdminUser(options, existing.id);
1941
+ return;
1942
+ }
1943
+ const created = await adminFetch(options, "/admin/users", {
1944
+ method: "POST",
1945
+ body: JSON.stringify({
1946
+ email: options.email,
1947
+ password: options.password,
1948
+ email_confirm: true,
1949
+ user_metadata: {
1950
+ display_name: "Dreamboard Local Dev"
1951
+ }
1952
+ })
1953
+ });
1954
+ if (created.ok) {
1955
+ return;
1956
+ }
1957
+ const retried = await findAdminUserByEmail(options);
1958
+ if (retried?.id) {
1959
+ await updateAdminUser(options, retried.id);
1960
+ return;
1961
+ }
1962
+ throw new Error(
1963
+ `Failed to create local Supabase user ${options.email} (${created.status}): ${JSON.stringify(
1964
+ created.data
1965
+ )}`
1966
+ );
1967
+ }
1968
+ async function updateAdminUser(options, userId) {
1969
+ const updated = await adminFetch(
1970
+ options,
1971
+ `/admin/users/${encodeURIComponent(userId)}`,
1972
+ {
1973
+ method: "PUT",
1974
+ body: JSON.stringify({
1975
+ password: options.password,
1976
+ email_confirm: true,
1977
+ user_metadata: {
1978
+ display_name: "Dreamboard Local Dev"
1979
+ }
1980
+ })
1981
+ }
1982
+ );
1983
+ if (!updated.ok) {
1984
+ throw new Error(
1985
+ `Failed to update local Supabase user ${options.email} (${updated.status}): ${JSON.stringify(
1986
+ updated.data
1987
+ )}`
1988
+ );
1989
+ }
1990
+ }
1991
+ async function findAdminUserByEmail(options) {
1992
+ const response = await adminFetch(
1993
+ options,
1994
+ "/admin/users?page=1&per_page=200",
1995
+ {
1996
+ method: "GET"
1997
+ }
1998
+ );
1999
+ if (!response.ok) {
2000
+ throw new Error(
2001
+ `Failed to list local Supabase users (${response.status}): ${JSON.stringify(
2002
+ response.data
2003
+ )}`
2004
+ );
2005
+ }
2006
+ const users = response.data?.users ?? [];
2007
+ return users.find(
2008
+ (user) => user.email?.toLowerCase() === options.email.toLowerCase()
2009
+ ) ?? null;
2010
+ }
2011
+ async function postAuth(options, path29, body) {
2012
+ const response = await fetch(`${options.authBaseUrl}${path29}`, {
2013
+ method: "POST",
2014
+ headers: options.headers,
2015
+ body: JSON.stringify(body)
2016
+ });
2017
+ return {
2018
+ ok: response.ok,
2019
+ status: response.status,
2020
+ data: await readJson(response)
2021
+ };
2022
+ }
2023
+ async function adminFetch(options, path29, init2) {
2024
+ const response = await fetch(`${options.authBaseUrl}${path29}`, {
2025
+ ...init2,
2026
+ headers: options.headers
2027
+ });
2028
+ return {
2029
+ ok: response.ok,
2030
+ status: response.status,
2031
+ data: await readJson(response)
2032
+ };
2033
+ }
2034
+ async function readJson(response) {
2035
+ const text = await response.text();
2036
+ if (text.trim().length === 0) return null;
2037
+ return JSON.parse(text);
2038
+ }
2039
+ function isLocalSupabaseUrl(rawUrl) {
2040
+ const url = new URL(rawUrl);
2041
+ return url.hostname === "127.0.0.1" || url.hostname === "localhost";
2042
+ }
2043
+ function valueOrUndefined(value) {
2044
+ const trimmed = value?.trim();
2045
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
2046
+ }
2047
+
1862
2048
  // src/config/resolve.ts
1863
2049
  var LOGIN_HINT = "Run `dreamboard login` to authenticate again.";
1864
2050
  function resolveConfig(globalConfig, flags, project, credentials) {
@@ -1866,14 +2052,15 @@ function resolveConfig(globalConfig, flags, project, credentials) {
1866
2052
  assertPublicRuntimeFlags(flags);
1867
2053
  }
1868
2054
  const envEnvironment = IS_PUBLISHED_BUILD ? void 0 : environmentFromProcess();
1869
- const environment = IS_PUBLISHED_BUILD ? PUBLISHED_ENVIRONMENT : flags.env || envEnvironment || globalConfig.environment || "dev";
2055
+ const projectEnvironment = IS_PUBLISHED_BUILD ? void 0 : project?.environment;
2056
+ const environment = IS_PUBLISHED_BUILD ? PUBLISHED_ENVIRONMENT : flags.env || envEnvironment || projectEnvironment || globalConfig.environment || "dev";
1870
2057
  const envConfig = ENVIRONMENT_CONFIGS[environment];
1871
2058
  const publishedEnvConfig = ENVIRONMENT_CONFIGS[PUBLISHED_ENVIRONMENT];
1872
- const hasExplicitEnvironmentOverride = !IS_PUBLISHED_BUILD && Boolean(flags.env || envEnvironment);
2059
+ const hasExplicitEnvironmentOverride = !IS_PUBLISHED_BUILD && Boolean(flags.env || envEnvironment || projectEnvironment);
1873
2060
  const resolvedApiBaseUrl = IS_PUBLISHED_BUILD ? publishedEnvConfig?.apiBaseUrl ?? DEFAULT_API_BASE_URL : hasExplicitEnvironmentOverride ? envConfig?.apiBaseUrl || DEFAULT_API_BASE_URL : project?.apiBaseUrl || envConfig?.apiBaseUrl || DEFAULT_API_BASE_URL;
1874
- const apiBaseUrl = valueOrUndefined(process.env.DREAMBOARD_API_BASE_URL) ?? resolvedApiBaseUrl;
2061
+ const apiBaseUrl = valueOrUndefined2(process.env.DREAMBOARD_API_BASE_URL) ?? resolvedApiBaseUrl;
1875
2062
  const resolvedWebBaseUrl = IS_PUBLISHED_BUILD ? publishedEnvConfig?.webBaseUrl ?? DEFAULT_WEB_BASE_URL : hasExplicitEnvironmentOverride ? envConfig?.webBaseUrl || DEFAULT_WEB_BASE_URL : project?.webBaseUrl || envConfig?.webBaseUrl || DEFAULT_WEB_BASE_URL;
1876
- const webBaseUrl = valueOrUndefined(process.env.DREAMBOARD_WEB_BASE_URL) ?? resolvedWebBaseUrl;
2063
+ const webBaseUrl = valueOrUndefined2(process.env.DREAMBOARD_WEB_BASE_URL) ?? resolvedWebBaseUrl;
1877
2064
  const supabaseUrl = envConfig?.supabaseUrl;
1878
2065
  const supabaseAnonKey = envConfig?.supabaseAnonKey;
1879
2066
  const snapshot = buildCredentialSnapshot(flags, credentials);
@@ -1889,10 +2076,10 @@ function resolveConfig(globalConfig, flags, project, credentials) {
1889
2076
  };
1890
2077
  }
1891
2078
  function buildCredentialSnapshot(flags, storedCredentials) {
1892
- const flagToken = valueOrUndefined(flags.token);
1893
- const agentEnvToken = valueOrUndefined(process.env.DREAMBOARD_AGENT_TOKEN);
1894
- const envToken = valueOrUndefined(process.env.DREAMBOARD_TOKEN);
1895
- const envRefreshToken = valueOrUndefined(
2079
+ const flagToken = valueOrUndefined2(flags.token);
2080
+ const agentEnvToken = valueOrUndefined2(process.env.DREAMBOARD_AGENT_TOKEN);
2081
+ const envToken = valueOrUndefined2(process.env.DREAMBOARD_TOKEN);
2082
+ const envRefreshToken = valueOrUndefined2(
1896
2083
  process.env.DREAMBOARD_REFRESH_TOKEN
1897
2084
  );
1898
2085
  if (IS_PUBLISHED_BUILD) {
@@ -1924,7 +2111,7 @@ function buildCredentialSnapshot(flags, storedCredentials) {
1924
2111
  };
1925
2112
  }
1926
2113
  function environmentFromProcess() {
1927
- const value = valueOrUndefined(process.env.DREAMBOARD_ENV);
2114
+ const value = valueOrUndefined2(process.env.DREAMBOARD_ENV);
1928
2115
  if (!value) return void 0;
1929
2116
  if (value === "local" || value === "dev" || value === "prod") {
1930
2117
  return value;
@@ -1940,7 +2127,7 @@ function assertPublicRuntimeFlags(flags) {
1940
2127
  "The published Dreamboard CLI is production-only and does not accept `--env`."
1941
2128
  );
1942
2129
  }
1943
- if (valueOrUndefined(flags.token) || argv2.includes("--token")) {
2130
+ if (valueOrUndefined2(flags.token) || argv2.includes("--token")) {
1944
2131
  throw new Error(
1945
2132
  "Direct JWT injection is not supported in the published Dreamboard CLI. Use `dreamboard login` so the CLI can store and refresh your session."
1946
2133
  );
@@ -1981,6 +2168,12 @@ async function ensureEffectiveAccessToken(config) {
1981
2168
  const expiry = getAccessTokenExpiry(config.authToken);
1982
2169
  const isExpired = expiry !== null && expiry.getTime() <= Date.now();
1983
2170
  if (isExpired) {
2171
+ if (shouldRepairLocalDevSession(config)) {
2172
+ const repaired = await repairLocalDevSession();
2173
+ if (repaired) {
2174
+ return repaired.accessToken;
2175
+ }
2176
+ }
1984
2177
  throw new Error(
1985
2178
  `Access token refresh failed: ${result.message}. ${LOGIN_HINT}`
1986
2179
  );
@@ -1993,6 +2186,12 @@ async function ensureEffectiveAccessToken(config) {
1993
2186
  }
1994
2187
  } catch (error) {
1995
2188
  if (error instanceof PermanentRefreshError) {
2189
+ if (shouldRepairLocalDevSession(config)) {
2190
+ const repaired = await repairLocalDevSession();
2191
+ if (repaired) {
2192
+ return repaired.accessToken;
2193
+ }
2194
+ }
1996
2195
  throw new Error(formatStoredSessionInvalidMessage(error.message));
1997
2196
  }
1998
2197
  throw error;
@@ -2029,7 +2228,7 @@ function requireAuth(config) {
2029
2228
  );
2030
2229
  }
2031
2230
  }
2032
- function valueOrUndefined(value) {
2231
+ function valueOrUndefined2(value) {
2033
2232
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
2034
2233
  }
2035
2234
  function isInvalidRefreshTokenMessage(message) {
@@ -2122,7 +2321,8 @@ var joinCommandArgsSchema = configFlagsSchema.extend({
2122
2321
  });
2123
2322
  var loginCommandArgsSchema = configFlagsSchema;
2124
2323
  var configCommandArgsSchema = configFlagsSchema.extend({
2125
- action: external_exports.string().optional().default("show")
2324
+ action: external_exports.string().optional().default("show"),
2325
+ scope: external_exports.enum(["global", "workspace"]).optional().default("global")
2126
2326
  });
2127
2327
  var authCommandArgsSchema = external_exports.object({
2128
2328
  action: external_exports.enum(["set", "clear", "login", "env", "status"]),
@@ -8724,7 +8924,7 @@ function validateSlotHostsAndHomes(manifest) {
8724
8924
  }
8725
8925
  slotIdsByHostKey.set(`die:${seed.id}`, new Set(slotIds));
8726
8926
  }
8727
- const validateHome = (home, path28) => {
8927
+ const validateHome = (home, path29) => {
8728
8928
  if (home?.type !== "slot") {
8729
8929
  return;
8730
8930
  }
@@ -8732,13 +8932,13 @@ function validateSlotHostsAndHomes(manifest) {
8732
8932
  const slotIds = slotIdsByHostKey.get(hostKey);
8733
8933
  if (!slotIds) {
8734
8934
  issues.push(
8735
- `${path28}.host: Unknown strict slot host '${home.host.kind}:${home.host.id}'. Hosts must be singleton piece/die seeds whose type declares slots.`
8935
+ `${path29}.host: Unknown strict slot host '${home.host.kind}:${home.host.id}'. Hosts must be singleton piece/die seeds whose type declares slots.`
8736
8936
  );
8737
8937
  return;
8738
8938
  }
8739
8939
  if (!slotIds.has(home.slotId)) {
8740
8940
  issues.push(
8741
- `${path28}.slotId: Unknown slot '${home.slotId}' for host '${home.host.kind}:${home.host.id}'.`
8941
+ `${path29}.slotId: Unknown slot '${home.slotId}' for host '${home.host.kind}:${home.host.id}'.`
8742
8942
  );
8743
8943
  }
8744
8944
  };
@@ -8804,20 +9004,20 @@ function validatePlayerScopedSeedHomes(manifest) {
8804
9004
  const zoneScopeById = new Map(
8805
9005
  (manifest.zones ?? []).map((zone) => [zone.id, zone.scope])
8806
9006
  );
8807
- const validateSeedHome = (seed, path28, label) => {
9007
+ const validateSeedHome = (seed, path29, label) => {
8808
9008
  const authoredId = seed.id ?? seed.typeId;
8809
9009
  if (seed.ownerId) {
8810
9010
  return;
8811
9011
  }
8812
9012
  if (homeTargetsBoard(seed.home) && boardScopeById.get(seed.home.boardId) === "perPlayer") {
8813
9013
  issues.push(
8814
- `${path28}.boardId: ${label} '${authoredId}' requires ownerId because board '${seed.home.boardId}' has scope 'perPlayer'. Add ownerId to resolve the player-scoped destination.`
9014
+ `${path29}.boardId: ${label} '${authoredId}' requires ownerId because board '${seed.home.boardId}' has scope 'perPlayer'. Add ownerId to resolve the player-scoped destination.`
8815
9015
  );
8816
9016
  return;
8817
9017
  }
8818
9018
  if (seed.home?.type === "zone" && zoneScopeById.get(seed.home.zoneId) === "perPlayer") {
8819
9019
  issues.push(
8820
- `${path28}.zoneId: ${label} '${authoredId}' requires ownerId because zone '${seed.home.zoneId}' has scope 'perPlayer'. Add ownerId to resolve the player-scoped destination.`
9020
+ `${path29}.zoneId: ${label} '${authoredId}' requires ownerId because zone '${seed.home.zoneId}' has scope 'perPlayer'. Add ownerId to resolve the player-scoped destination.`
8821
9021
  );
8822
9022
  }
8823
9023
  };
@@ -9265,7 +9465,11 @@ var WORKSPACE_CODEGEN_OWNERSHIP = {
9265
9465
  "app/game.ts",
9266
9466
  "app/setup-profiles.ts",
9267
9467
  "app/reducer-support.ts",
9268
- "app/derived.ts"
9468
+ "app/derived.ts",
9469
+ "ui/interaction-routes.tsx",
9470
+ "ui/setup-screen.tsx",
9471
+ "ui/styles.ts",
9472
+ "ui/ui-contract-typing-smoke.tsx"
9269
9473
  ],
9270
9474
  seedFilePatterns: [{ prefix: "app/phases/", suffix: ".ts" }]
9271
9475
  },
@@ -9293,30 +9497,30 @@ function normalizeProjectPath(filePath) {
9293
9497
  return filePath.replace(/^\.\//, "").replace(/^\/+/, "").replace(/\\/g, "/");
9294
9498
  }
9295
9499
  function isAllowedGamePath(filePath) {
9296
- const path28 = normalizeProjectPath(filePath);
9297
- if (WORKSPACE_CODEGEN_OWNERSHIP.allowedPaths.rootFiles.includes(path28)) {
9500
+ const path29 = normalizeProjectPath(filePath);
9501
+ if (WORKSPACE_CODEGEN_OWNERSHIP.allowedPaths.rootFiles.includes(path29)) {
9298
9502
  return true;
9299
9503
  }
9300
9504
  return WORKSPACE_CODEGEN_OWNERSHIP.allowedPaths.directoryPrefixes.some(
9301
- (prefix) => path28.startsWith(prefix)
9505
+ (prefix) => path29.startsWith(prefix)
9302
9506
  );
9303
9507
  }
9304
9508
  function isAuthoritativeGeneratedPath(filePath) {
9305
- const path28 = normalizeProjectPath(filePath);
9306
- return WORKSPACE_CODEGEN_OWNERSHIP.dynamic.generatedFiles.includes(path28);
9509
+ const path29 = normalizeProjectPath(filePath);
9510
+ return WORKSPACE_CODEGEN_OWNERSHIP.dynamic.generatedFiles.includes(path29);
9307
9511
  }
9308
9512
  function isDynamicSeedPath(filePath) {
9309
- const path28 = normalizeProjectPath(filePath);
9310
- if (WORKSPACE_CODEGEN_OWNERSHIP.dynamic.seedFiles.includes(path28)) {
9513
+ const path29 = normalizeProjectPath(filePath);
9514
+ if (WORKSPACE_CODEGEN_OWNERSHIP.dynamic.seedFiles.includes(path29)) {
9311
9515
  return true;
9312
9516
  }
9313
9517
  return WORKSPACE_CODEGEN_OWNERSHIP.dynamic.seedFilePatterns.some(
9314
- (pattern) => path28.startsWith(pattern.prefix) && path28.endsWith(pattern.suffix)
9518
+ (pattern) => path29.startsWith(pattern.prefix) && path29.endsWith(pattern.suffix)
9315
9519
  );
9316
9520
  }
9317
9521
  function isLibraryPath(filePath) {
9318
- const path28 = normalizeProjectPath(filePath);
9319
- return isAuthoritativeGeneratedPath(path28);
9522
+ const path29 = normalizeProjectPath(filePath);
9523
+ return isAuthoritativeGeneratedPath(path29);
9320
9524
  }
9321
9525
 
9322
9526
  // ../../packages/workspace-codegen/src/seeds.ts
@@ -9332,9 +9536,11 @@ import {
9332
9536
  createClientParamSchemasByPhase,
9333
9537
  } from "@dreamboard/app-sdk/reducer";
9334
9538
  import type {
9335
- ClientParamsOfInteractionOfDefinition,
9336
- DefaultedClientParamKeysOfInteractionOfDefinition,
9337
- InteractionIdOfDefinition,
9539
+ CardInputZoneIdsOfDefinition,
9540
+ ClientParamsOfInteractionOfDefinition,
9541
+ DefaultedClientParamKeysOfInteractionOfDefinition,
9542
+ InputKeysWithCollectorKindOfDefinition,
9543
+ InteractionIdOfDefinition,
9338
9544
  InteractionIdOfDefinitionPhase,
9339
9545
  PhaseNamesOfDefinition,
9340
9546
  StageNamesOfDefinitionPhase,
@@ -9342,42 +9548,47 @@ import type {
9342
9548
  ViewOfDefinition,
9343
9549
  } from "@dreamboard/app-sdk/reducer";
9344
9550
  import {
9345
- createDreamboardUI,
9346
- createResourceCounter,
9347
- InteractionField as InteractionFieldGeneric,
9348
- type BoardHexGridProps as BoardHexGridPropsGeneric,
9349
- type BoardHexViewProps as BoardHexViewPropsGeneric,
9350
- type BoardSpaceIdOf,
9351
- type ClientParamSchemaMap,
9352
- type InteractionDescriptor,
9353
- type InteractionFieldProps as InteractionFieldPropsGeneric,
9354
- type InteractionFieldRenderMap,
9355
- type InteractionHandle,
9356
- type InteractionRouteMap as InteractionRouteMapGeneric,
9357
- type InteractionSwitchProps as InteractionSwitchPropsGeneric,
9358
- type InteractionSwitchRenderState,
9359
- type DreamboardUI,
9360
- type ResourceCounterComponents,
9361
- type ResourceDisplayConfig,
9362
- type TypedGame,
9363
- type UIContract,
9364
- type UIRootProps,
9365
- type ZoneCardAtProps,
9366
- type ZoneCardRenderItem,
9367
- type ZoneListProps,
9368
- type ZonePileCardsProps,
9369
- } from "@dreamboard/ui-sdk";
9370
- import { createElement, type ReactElement, type ReactNode } from "react";
9371
- import {
9372
- literals,
9373
- staticBoards,
9374
- type CardId,
9375
- type CardProperties,
9376
- type CardType,
9377
- type EdgeId,
9551
+ type BoardSpaceIdOf,
9552
+ type InteractionDescriptor,
9553
+ } from "@dreamboard/ui-sdk";
9554
+ import {
9555
+ createWorkspaceUIContract,
9556
+ type BoardHexGridProps as BoardHexGridPropsGeneric,
9557
+ type BoardHexViewProps as BoardHexViewPropsGeneric,
9558
+ type BoardSpaceTargetProps as BoardSpaceTargetPropsGeneric,
9559
+ type ClientParamSchemaMap,
9560
+ type DreamboardUI,
9561
+ type ResourceCounterComponents,
9562
+ type TypedGame,
9563
+ type UIContract,
9564
+ type UIRootProps,
9565
+ type WorkspaceBoardSurface,
9566
+ type WorkspaceBoardTargetInputSlot,
9567
+ type WorkspaceCardCollectionSurface,
9568
+ type WorkspaceCardInputSlot,
9569
+ type WorkspaceFormInputSlot,
9570
+ type WorkspaceHandSurface,
9571
+ type WorkspaceInteractionSlotComponent,
9572
+ type WorkspacePileSurface,
9573
+ type ZoneCardRenderItem,
9574
+ type ZoneListProps,
9575
+ } from "@dreamboard/ui-sdk/workspace-contract";
9576
+ import { type ButtonHTMLAttributes, type ReactElement, type ReactNode } from "react";
9577
+ import {
9578
+ boardHelpers,
9579
+ literals,
9580
+ staticBoards,
9581
+ type BoardBaseId,
9582
+ type CardId,
9583
+ type CardProperties,
9584
+ type CardType,
9585
+ type EdgeId,
9378
9586
  type PlayerId,
9379
9587
  type ResourceId,
9380
9588
  type SpaceId,
9589
+ type TiledBoardId,
9590
+ type TiledEdgeState,
9591
+ type TiledVertexState,
9381
9592
  type VertexId,
9382
9593
  type ZoneId as ManifestZoneId,
9383
9594
  } from "../manifest-contract";
@@ -9466,6 +9677,14 @@ type PlayerCardZoneId = {
9466
9677
  : Z;
9467
9678
  }[(typeof literals.playerZoneIds)[number]];
9468
9679
 
9680
+ type SharedCardZoneId = {
9681
+ [Z in (typeof literals.sharedZoneIds)[number]]: (typeof literals.cardSetIdsBySharedZoneId)[Z] extends readonly []
9682
+ ? never
9683
+ : Z;
9684
+ }[(typeof literals.sharedZoneIds)[number]];
9685
+
9686
+ type CardZoneId = PlayerCardZoneId | SharedCardZoneId;
9687
+
9469
9688
  /** JS-friendly keys for per-player zones that can contain cards. */
9470
9689
  export type WorkspacePlayerCardZoneKey = CamelCase<PlayerCardZoneId>;
9471
9690
 
@@ -9473,6 +9692,11 @@ export type WorkspacePlayerCardZoneKey = CamelCase<PlayerCardZoneId>;
9473
9692
  export type InteractionDescriptorFor<Key extends InteractionKey> =
9474
9693
  InteractionDescriptor<Key>;
9475
9694
 
9695
+ export type InteractionItem<Key extends InteractionKey> = {
9696
+ readonly interaction: Key;
9697
+ readonly descriptor: InteractionDescriptorFor<Key>;
9698
+ };
9699
+
9476
9700
  /**
9477
9701
  * Params shape for a phase-qualified interaction key. Drives strong typing
9478
9702
  * for component-first interaction state, forms, and submits.
@@ -9488,60 +9712,528 @@ type InteractionParamsShape<Key extends InteractionKey> =
9488
9712
  ? InteractionParamsOf<Key>
9489
9713
  : Record<string, unknown>;
9490
9714
 
9715
+ type InteractionCollectorKind =
9716
+ | "form"
9717
+ | "board-vertex"
9718
+ | "board-edge"
9719
+ | "board-tile"
9720
+ | "board-space"
9721
+ | "card"
9722
+ | "prompt";
9723
+
9724
+ type AuthoredInteractionInputKeysOf<Key extends InteractionKey> =
9725
+ InputKeysWithCollectorKindOfDefinition<
9726
+ GameDefinition,
9727
+ PhaseOfInteractionKey<Key>,
9728
+ IdOfInteractionKey<Key>,
9729
+ InteractionCollectorKind
9730
+ > extends infer Input
9731
+ ? string extends Input
9732
+ ? never
9733
+ : Input & string
9734
+ : never;
9735
+
9491
9736
  type InteractionInputKeysOf<Key extends InteractionKey> =
9492
- string extends keyof InteractionParamsOf<Key>
9493
- ? never
9494
- : keyof InteractionParamsOf<Key> & string;
9737
+ | (string extends keyof InteractionParamsOf<Key>
9738
+ ? never
9739
+ : keyof InteractionParamsOf<Key> & string)
9740
+ | AuthoredInteractionInputKeysOf<Key>
9741
+ | (InteractionDefaultedKeysOf<Key> & string);
9495
9742
 
9496
9743
  type InteractionHandleDefaultedKeys<Key extends InteractionKey> = Extract<
9497
9744
  InteractionDefaultedKeysOf<Key>,
9498
9745
  keyof InteractionParamsShape<Key> & string
9499
9746
  >;
9500
9747
 
9501
- type RequiredInteractionInputKeysOf<Key extends InteractionKey> = Exclude<
9502
- InteractionInputKeysOf<Key>,
9503
- InteractionHandleDefaultedKeys<Key>
9504
- >;
9748
+ type RequiredInteractionInputKeysOf<Key extends InteractionKey> =
9749
+ InteractionInputKeysOf<Key>;
9505
9750
 
9506
9751
  export type RequiredInteractionInputKey<Key extends InteractionKey> =
9507
9752
  RequiredInteractionInputKeysOf<Key>;
9508
9753
 
9509
- export type ZeroInputInteractionKey = {
9510
- [K in InteractionKey]: InteractionInputKeysOf<K> extends never
9511
- ? K
9754
+ type InteractionSlotComponent<Props = object> = (
9755
+ props: Props extends { children: unknown }
9756
+ ? Props
9757
+ : Props & { children?: ReactNode },
9758
+ ) => ReactElement | null;
9759
+
9760
+ type InteractionDefaultInputSlot = {
9761
+ readonly Default: InteractionSlotComponent;
9762
+ };
9763
+
9764
+ type InteractionValueInputSlot<Value = unknown> = {
9765
+ readonly Value: InteractionSlotComponent<{
9766
+ children: (value: unknown | undefined) => ReactNode;
9767
+ }>;
9768
+ };
9769
+
9770
+ type InteractionFormInputSlot<Value = unknown> = {
9771
+ readonly Field: InteractionSlotComponent;
9772
+ readonly Options: InteractionSlotComponent<{
9773
+ children: (option: { value: Value; label: string }) => ReactNode;
9774
+ }>;
9775
+ };
9776
+
9777
+ type DreamboardSlotBrand<Meta> = {
9778
+ readonly __dreamboardSlot: Meta;
9779
+ };
9780
+
9781
+ type InteractionCardTargetInputSlot<Card extends string = string> = {
9782
+ readonly Card: InteractionSlotComponent<
9783
+ { value: string; swipe?: "auto" | "off" } & Omit<
9784
+ ButtonHTMLAttributes<HTMLButtonElement>,
9785
+ | "children"
9786
+ | "disabled"
9787
+ | "aria-disabled"
9788
+ | "aria-pressed"
9789
+ | "onClick"
9790
+ | "type"
9791
+ | "value"
9792
+ >
9793
+ >;
9794
+ readonly Cards: InteractionSlotComponent<{
9795
+ children: (card: { id: Card }) => ReactNode;
9796
+ }>;
9797
+ };
9798
+
9799
+ type InteractionBoardTargetInputSlot<Target extends string = string> = {
9800
+ readonly Target: InteractionSlotComponent<
9801
+ { value: string } & Omit<
9802
+ ButtonHTMLAttributes<HTMLButtonElement>,
9803
+ | "children"
9804
+ | "disabled"
9805
+ | "aria-disabled"
9806
+ | "aria-pressed"
9807
+ | "onClick"
9808
+ | "type"
9809
+ | "value"
9810
+ >
9811
+ >;
9812
+ };
9813
+
9814
+ type InteractionBoardSpaceTargetInputSlot<Space extends string = string> =
9815
+ InteractionBoardTargetInputSlot<Space>;
9816
+
9817
+ type InteractionBoardEdgeTargetInputSlot<Edge extends string = string> =
9818
+ InteractionBoardTargetInputSlot<Edge>;
9819
+
9820
+ type InteractionBoardVertexTargetInputSlot<Vertex extends string = string> =
9821
+ InteractionBoardTargetInputSlot<Vertex>;
9822
+
9823
+ type InteractionBoardTileTargetInputSlot<Tile extends string = string> =
9824
+ InteractionBoardTargetInputSlot<Tile>;
9825
+
9826
+ type InteractionSubmitSlot = {
9827
+ readonly Button: InteractionSlotComponent<
9828
+ Omit<
9829
+ ButtonHTMLAttributes<HTMLButtonElement>,
9830
+ "children" | "disabled" | "type" | "value"
9831
+ >
9832
+ >;
9833
+ };
9834
+
9835
+ type InteractionInputValue<
9836
+ Key extends InteractionKey,
9837
+ Input extends InteractionInputKeysOf<Key>,
9838
+ > = InteractionParamsShape<Key>[Input & keyof InteractionParamsShape<Key>];
9839
+
9840
+ type InteractionSlotValue<Value> = Value extends readonly (infer Item)[]
9841
+ ? InteractionSlotValue<Item>
9842
+ : Value extends { spaceId: infer Space extends string }
9843
+ ? Space
9844
+ : Value extends { edgeId: infer Edge extends string }
9845
+ ? Edge
9846
+ : Value extends { vertexId: infer Vertex extends string }
9847
+ ? Vertex
9848
+ : Value extends { tileId: infer Tile extends string }
9849
+ ? Tile
9850
+ : Value extends { cardId: infer Card extends string }
9851
+ ? Card
9852
+ : Value;
9853
+
9854
+ type InteractionSlotStringValue<Value> = Extract<
9855
+ InteractionSlotValue<Value>,
9856
+ string
9857
+ >;
9858
+
9859
+ type FormInteractionInputKey<Key extends InteractionKey> =
9860
+ InputKeysWithCollectorKindOfDefinition<
9861
+ GameDefinition,
9862
+ PhaseOfInteractionKey<Key>,
9863
+ IdOfInteractionKey<Key>,
9864
+ "form" | "prompt"
9865
+ > &
9866
+ InteractionInputKeysOf<Key>;
9867
+
9868
+ type CardTargetInteractionInputKey<Key extends InteractionKey> =
9869
+ InputKeysWithCollectorKindOfDefinition<
9870
+ GameDefinition,
9871
+ PhaseOfInteractionKey<Key>,
9872
+ IdOfInteractionKey<Key>,
9873
+ "card"
9874
+ > &
9875
+ InteractionInputKeysOf<Key>;
9876
+
9877
+ type CardTargetZoneIds<
9878
+ Key extends InteractionKey,
9879
+ Input extends InteractionInputKeysOf<Key>,
9880
+ > =
9881
+ CardInputZoneIdsOfDefinition<
9882
+ GameDefinition,
9883
+ PhaseOfInteractionKey<Key>,
9884
+ IdOfInteractionKey<Key>,
9885
+ Input & string
9886
+ > extends infer Zone extends string
9887
+ ? Extract<Zone, WorkspaceZoneId>
9888
+ : never;
9889
+
9890
+ type BoardSpaceTargetInteractionInputKey<Key extends InteractionKey> =
9891
+ InputKeysWithCollectorKindOfDefinition<
9892
+ GameDefinition,
9893
+ PhaseOfInteractionKey<Key>,
9894
+ IdOfInteractionKey<Key>,
9895
+ "board-space"
9896
+ > &
9897
+ InteractionInputKeysOf<Key>;
9898
+
9899
+ type BoardEdgeTargetInteractionInputKey<Key extends InteractionKey> =
9900
+ InputKeysWithCollectorKindOfDefinition<
9901
+ GameDefinition,
9902
+ PhaseOfInteractionKey<Key>,
9903
+ IdOfInteractionKey<Key>,
9904
+ "board-edge"
9905
+ > &
9906
+ InteractionInputKeysOf<Key>;
9907
+
9908
+ type BoardVertexTargetInteractionInputKey<Key extends InteractionKey> =
9909
+ InputKeysWithCollectorKindOfDefinition<
9910
+ GameDefinition,
9911
+ PhaseOfInteractionKey<Key>,
9912
+ IdOfInteractionKey<Key>,
9913
+ "board-vertex"
9914
+ > &
9915
+ InteractionInputKeysOf<Key>;
9916
+
9917
+ type BoardTileTargetInteractionInputKey<Key extends InteractionKey> =
9918
+ InputKeysWithCollectorKindOfDefinition<
9919
+ GameDefinition,
9920
+ PhaseOfInteractionKey<Key>,
9921
+ IdOfInteractionKey<Key>,
9922
+ "board-tile"
9923
+ > &
9924
+ InteractionInputKeysOf<Key>;
9925
+
9926
+ type InteractionDefaultSlotFor<
9927
+ Key extends InteractionKey,
9928
+ Input extends InteractionInputKeysOf<Key>,
9929
+ > = Input extends InteractionHandleDefaultedKeys<Key>
9930
+ ? InteractionDefaultInputSlot
9931
+ : object;
9932
+
9933
+ export type InteractionInputSlot<
9934
+ Key extends InteractionKey,
9935
+ Input extends InteractionInputKeysOf<Key>,
9936
+ > = InteractionValueInputSlot<InteractionInputValue<Key, Input>> &
9937
+ InteractionDefaultSlotFor<Key, Input> &
9938
+ (Input extends CardTargetInteractionInputKey<Key>
9939
+ ? InteractionCardTargetInputSlot<
9940
+ InteractionSlotStringValue<InteractionInputValue<Key, Input>>
9941
+ >
9942
+ : Input extends BoardSpaceTargetInteractionInputKey<Key>
9943
+ ? InteractionBoardSpaceTargetInputSlot<
9944
+ InteractionSlotStringValue<InteractionInputValue<Key, Input>>
9945
+ >
9946
+ : Input extends BoardEdgeTargetInteractionInputKey<Key>
9947
+ ? InteractionBoardEdgeTargetInputSlot<
9948
+ InteractionSlotStringValue<InteractionInputValue<Key, Input>>
9949
+ >
9950
+ : Input extends BoardVertexTargetInteractionInputKey<Key>
9951
+ ? InteractionBoardVertexTargetInputSlot<
9952
+ InteractionSlotStringValue<InteractionInputValue<Key, Input>>
9953
+ >
9954
+ : Input extends BoardTileTargetInteractionInputKey<Key>
9955
+ ? InteractionBoardTileTargetInputSlot<
9956
+ InteractionSlotStringValue<InteractionInputValue<Key, Input>>
9957
+ >
9958
+ : Input extends FormInteractionInputKey<Key>
9959
+ ? InteractionFormInputSlot<InteractionInputValue<Key, Input>>
9960
+ : never);
9961
+
9962
+ type FormSurfaceInputSlot<
9963
+ Key extends InteractionKey,
9964
+ Input extends FormInteractionInputKey<Key>,
9965
+ > = InteractionValueInputSlot<InteractionInputValue<Key, Input>> &
9966
+ InteractionDefaultSlotFor<Key, Input> &
9967
+ InteractionFormInputSlot<InteractionInputValue<Key, Input>> &
9968
+ DreamboardSlotBrand<{
9969
+ readonly kind: "form";
9970
+ readonly interaction: Key;
9971
+ readonly input: Input;
9972
+ }>;
9973
+
9974
+ type CardSurfaceInputSlot<Zones extends string> =
9975
+ InteractionValueInputSlot<unknown> &
9976
+ InteractionCardTargetInputSlot<WorkspaceCardId & string> &
9977
+ DreamboardSlotBrand<{
9978
+ readonly kind: "card";
9979
+ readonly zones: Zones;
9980
+ readonly selection: "one" | "many";
9981
+ }>;
9982
+
9983
+ type BoardSurfaceInputSlot<
9984
+ Kind extends "space" | "edge" | "vertex" | "tile",
9985
+ Target extends string = string,
9986
+ > =
9987
+ InteractionValueInputSlot<unknown> &
9988
+ InteractionBoardTargetInputSlot<Target> &
9989
+ DreamboardSlotBrand<{
9990
+ readonly kind: "board";
9991
+ readonly targetKind: Kind;
9992
+ }>;
9993
+
9994
+ type InteractionCollectorSlot<
9995
+ Key extends InteractionKey,
9996
+ Input extends InteractionInputKeysOf<Key>,
9997
+ > = Input extends CardTargetInteractionInputKey<Key>
9998
+ ? CardSurfaceInputSlot<CardTargetZoneIds<Key, Input>>
9999
+ : Input extends BoardSpaceTargetInteractionInputKey<Key>
10000
+ ? BoardSurfaceInputSlot<"space", string>
10001
+ : Input extends BoardEdgeTargetInteractionInputKey<Key>
10002
+ ? BoardSurfaceInputSlot<"edge", string>
10003
+ : Input extends BoardVertexTargetInteractionInputKey<Key>
10004
+ ? BoardSurfaceInputSlot<"vertex", string>
10005
+ : Input extends BoardTileTargetInteractionInputKey<Key>
10006
+ ? BoardSurfaceInputSlot<"tile", string>
10007
+ : Input extends FormInteractionInputKey<Key>
10008
+ ? FormSurfaceInputSlot<Key, Input>
10009
+ : never;
10010
+
10011
+ type InteractionKeysWithInput<Input extends InteractionInputKey> = {
10012
+ [Key in InteractionKey]: Input extends InteractionInputKeysOf<Key>
10013
+ ? Key
9512
10014
  : never;
9513
10015
  }[InteractionKey];
9514
10016
 
9515
- export type InputInteractionKey = Exclude<
9516
- InteractionKey,
9517
- ZeroInputInteractionKey
10017
+ type InteractionInputSlotByName<Input extends InteractionInputKey> =
10018
+ InteractionInputSlot<
10019
+ InteractionKeysWithInput<Input>,
10020
+ Input & InteractionInputKeysOf<InteractionKeysWithInput<Input>>
10021
+ >;
10022
+
10023
+ type InteractionFormInputSlotByName<Input extends InteractionInputKey> =
10024
+ InteractionInputSlot<
10025
+ {
10026
+ [Key in InteractionKey]: Input extends FormInteractionInputKey<Key>
10027
+ ? Key
10028
+ : never;
10029
+ }[InteractionKey],
10030
+ Input &
10031
+ InteractionInputKeysOf<
10032
+ {
10033
+ [Key in InteractionKey]: Input extends FormInteractionInputKey<Key>
10034
+ ? Key
10035
+ : never;
10036
+ }[InteractionKey]
10037
+ >
10038
+ >;
10039
+
10040
+ type InteractionInputsForCollectorKind<
10041
+ Key extends InteractionKey,
10042
+ CollectorKind extends InteractionCollectorKind,
10043
+ > = {
10044
+ [Input in InteractionInputKeysOf<Key>]: CollectorKind extends "form"
10045
+ ? Input extends FormInteractionInputKey<Key>
10046
+ ? Input
10047
+ : never
10048
+ : CollectorKind extends "card"
10049
+ ? Input extends CardTargetInteractionInputKey<Key>
10050
+ ? Input
10051
+ : never
10052
+ : CollectorKind extends "board-space"
10053
+ ? Input extends BoardSpaceTargetInteractionInputKey<Key>
10054
+ ? Input
10055
+ : never
10056
+ : CollectorKind extends "board-edge"
10057
+ ? Input extends BoardEdgeTargetInteractionInputKey<Key>
10058
+ ? Input
10059
+ : never
10060
+ : CollectorKind extends "board-vertex"
10061
+ ? Input extends BoardVertexTargetInteractionInputKey<Key>
10062
+ ? Input
10063
+ : never
10064
+ : CollectorKind extends "board-tile"
10065
+ ? Input extends BoardTileTargetInteractionInputKey<Key>
10066
+ ? Input
10067
+ : never
10068
+ : never;
10069
+ }[InteractionInputKeysOf<Key>];
10070
+
10071
+ type InteractionKeysForCollectorKind<
10072
+ CollectorKind extends InteractionCollectorKind,
10073
+ > = {
10074
+ [Key in InteractionKey]: InteractionInputsForCollectorKind<
10075
+ Key,
10076
+ CollectorKind
10077
+ > extends never
10078
+ ? never
10079
+ : Key;
10080
+ }[InteractionKey];
10081
+
10082
+ type InteractionInputSlotByCollectorKind<
10083
+ CollectorKind extends InteractionCollectorKind,
10084
+ > = InteractionInputSlot<
10085
+ InteractionKeysForCollectorKind<CollectorKind>,
10086
+ InteractionInputsForCollectorKind<
10087
+ InteractionKeysForCollectorKind<CollectorKind>,
10088
+ CollectorKind
10089
+ >
9518
10090
  >;
9519
10091
 
9520
- export type InteractionRouteRenderState<Key extends InteractionKey> =
9521
- Omit<InteractionSwitchRenderState<Key>, "descriptor"> & {
9522
- descriptor: InteractionDescriptorFor<Key>;
10092
+ export type InteractionFormSurface<Key extends InteractionKey> = {
10093
+ readonly Root: InteractionSlotComponent;
10094
+ readonly Form: DreamboardUI<typeof uiContract>["Interaction"]["Form"];
10095
+ readonly Dialog: DreamboardUI<typeof uiContract>["Interaction"]["Dialog"];
10096
+ readonly State: DreamboardUI<typeof uiContract>["Interaction"]["State"];
10097
+ readonly Arm: DreamboardUI<typeof uiContract>["Interaction"]["Trigger"];
10098
+ readonly Submit: DreamboardUI<typeof uiContract>["Interaction"]["Submit"];
10099
+ readonly Field: <Input extends FormInteractionInputKey<Key>>(
10100
+ props: { input: Input; children?: ReactNode },
10101
+ ) => ReactElement | null;
10102
+ readonly slot: {
10103
+ readonly [Input in FormInteractionInputKey<Key>]: FormSurfaceInputSlot<
10104
+ Key,
10105
+ Input
10106
+ >;
9523
10107
  };
10108
+ };
10109
+
10110
+ type BoardSurfaceSpaceProps<Target extends string> = {
10111
+ value: Target;
10112
+ children?: ReactNode;
10113
+ } & Omit<BoardSpaceTargetPropsGeneric<Target>, "value" | "children">;
10114
+
10115
+ export type BoardSurfaceBoardId = BoardBaseId & string;
9524
10116
 
9525
- export type InteractionRouteMap = {
9526
- [K in InteractionKey]: (state: InteractionRouteRenderState<K>) => ReactNode;
10117
+ type BoardSurfaceRuntimeBoardId<Board extends BoardSurfaceBoardId> =
10118
+ ReturnType<typeof boardHelpers.boardIdsForBase<Board>>[number] & string;
10119
+
10120
+ type BoardSurfaceSpaceId<Board extends BoardSurfaceBoardId> =
10121
+ ReturnType<
10122
+ typeof boardHelpers.spaceIds<BoardSurfaceRuntimeBoardId<Board>>
10123
+ >[number] &
10124
+ string;
10125
+
10126
+ type BoardSurfaceTiledBoardId<Board extends BoardSurfaceBoardId> = Extract<
10127
+ BoardSurfaceRuntimeBoardId<Board>,
10128
+ TiledBoardId
10129
+ >;
10130
+
10131
+ type BoardSurfaceEdgeId<Board extends BoardSurfaceBoardId> =
10132
+ TiledEdgeState<BoardSurfaceTiledBoardId<Board>> extends { id: infer Id }
10133
+ ? Id & string
10134
+ : EdgeId & string;
10135
+
10136
+ type BoardSurfaceVertexId<Board extends BoardSurfaceBoardId> =
10137
+ TiledVertexState<BoardSurfaceTiledBoardId<Board>> extends { id: infer Id }
10138
+ ? Id & string
10139
+ : VertexId & string;
10140
+
10141
+ export type BoardSurface<Board extends BoardSurfaceBoardId = BoardSurfaceBoardId> = {
10142
+ readonly Root: InteractionSlotComponent;
10143
+ readonly Space: <Target extends BoardSurfaceSpaceId<Board>>(
10144
+ props: BoardSurfaceSpaceProps<Target>,
10145
+ ) => ReactElement;
10146
+ readonly slot: {
10147
+ readonly space: BoardSurfaceInputSlot<
10148
+ "space",
10149
+ BoardSurfaceSpaceId<Board>
10150
+ >;
10151
+ readonly playerSpace: BoardSurfaceInputSlot<
10152
+ "space",
10153
+ BoardSurfaceSpaceId<Board>
10154
+ >;
10155
+ readonly edge: BoardSurfaceInputSlot<"edge", BoardSurfaceEdgeId<Board>>;
10156
+ readonly vertex: BoardSurfaceInputSlot<
10157
+ "vertex",
10158
+ BoardSurfaceVertexId<Board>
10159
+ >;
10160
+ readonly tile: BoardSurfaceInputSlot<"tile", BoardSurfaceSpaceId<Board>>;
10161
+ };
9527
10162
  };
9528
10163
 
9529
- type ExactInteractionRoutes<Routes extends InteractionRouteMap> = Routes &
9530
- Record<Exclude<keyof Routes, keyof InteractionRouteMap>, never>;
10164
+ type ZoneCardsComponent = InteractionSlotComponent<
10165
+ Omit<ZoneListProps, "children" | "empty"> & {
10166
+ empty?: ReactNode;
10167
+ children: (card: WorkspaceZoneCard) => ReactNode;
10168
+ }
10169
+ >;
9531
10170
 
9532
- export function defineInteractionRoutes<const Routes extends InteractionRouteMap>(
9533
- routes: ExactInteractionRoutes<Routes>,
9534
- ): InteractionRouteMap {
9535
- return routes;
9536
- }
10171
+ type ZoneCardComponent = InteractionSlotComponent<
10172
+ Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" | "value"> & {
10173
+ card: WorkspaceZoneCard;
10174
+ swipe?: "auto" | "off";
10175
+ }
10176
+ >;
9537
10177
 
9538
- type InteractionSwitchProps = Omit<
9539
- InteractionSwitchPropsGeneric<InteractionKey>,
9540
- "routes"
9541
- > & {
9542
- routes: InteractionRouteMap;
10178
+ export type HandSurface<Zones extends readonly WorkspaceZoneId[] = readonly WorkspaceZoneId[]> = {
10179
+ readonly Hand: ZoneCardsComponent;
10180
+ readonly Card: ZoneCardComponent;
10181
+ readonly slot: {
10182
+ readonly card: CardSurfaceInputSlot<Zones[number] & string>;
10183
+ };
10184
+ };
10185
+
10186
+ export type PileSurface<Zones extends readonly WorkspaceZoneId[] = readonly WorkspaceZoneId[]> = {
10187
+ readonly Pile: ZoneCardsComponent;
10188
+ readonly Card: ZoneCardComponent;
10189
+ };
10190
+
10191
+ export type CardCollectionSurface<Zones extends readonly WorkspaceZoneId[] = readonly WorkspaceZoneId[]> = {
10192
+ readonly Collection: ZoneCardsComponent;
10193
+ readonly Card: ZoneCardComponent;
10194
+ readonly slot: {
10195
+ readonly card: CardSurfaceInputSlot<Zones[number] & string>;
10196
+ };
10197
+ };
10198
+
10199
+ export type InteractionFormInputs<Key extends InteractionKey> = {
10200
+ [Input in InteractionInputKeysOf<Key>]: (
10201
+ slot: InteractionInputSlot<Key, Input>,
10202
+ ) => ReactNode;
10203
+ };
10204
+
10205
+ export type InteractionCollectSlots<Key extends InteractionKey> = {
10206
+ [Input in RequiredInteractionInputKeysOf<Key>]: InteractionCollectorSlot<
10207
+ Key,
10208
+ Input
10209
+ >;
9543
10210
  };
9544
10211
 
10212
+ export type InteractionRoute<Key extends InteractionKey> = {
10213
+ readonly collect: InteractionCollectSlots<Key>;
10214
+ };
10215
+
10216
+ export type InteractionRoutes = {
10217
+ [Key in InteractionKey]: InteractionRoute<Key>;
10218
+ };
10219
+
10220
+ export type InteractionRoutesProps = {
10221
+ routes: InteractionRoutes;
10222
+ fallback?: ReactNode;
10223
+ includeUnavailable?: boolean | null;
10224
+ };
10225
+
10226
+ export type ZeroInputInteractionKey = {
10227
+ [K in InteractionKey]: InteractionInputKeysOf<K> extends never
10228
+ ? K
10229
+ : never;
10230
+ }[InteractionKey];
10231
+
10232
+ export type InputInteractionKey = Exclude<
10233
+ InteractionKey,
10234
+ ZeroInputInteractionKey
10235
+ >;
10236
+
9545
10237
  type InteractionInputKeyOf<Key extends InteractionKey> =
9546
10238
  Key extends InteractionKey ? InteractionInputKeysOf<Key> : never;
9547
10239
 
@@ -9645,17 +10337,14 @@ export type HexBoardGridProps<
9645
10337
  "board" | "interactions"
9646
10338
  > & {
9647
10339
  board: Id;
9648
- interactions?:
9649
- | "auto"
9650
- | false
9651
- | {
9652
- edge?: readonly InteractionKey[];
9653
- vertex?: readonly InteractionKey[];
9654
- space?: readonly InteractionKey[];
9655
- };
9656
10340
  };
9657
10341
 
9658
- type WorkspaceBoard = Omit<DreamboardUI<typeof uiContract>["Board"], "HexView" | "HexGrid"> & {
10342
+ type WorkspaceBoard = {
10343
+ useSurface(name: string): BoardSurface;
10344
+ useSurface<const Board extends BoardSurfaceBoardId>(
10345
+ name: string,
10346
+ options: { board: Board },
10347
+ ): BoardSurface<Board>;
9659
10348
  HexView<
9660
10349
  const Id extends HexBoardId,
9661
10350
  const TSpaceView extends { id: HexBoardSpaceId<Id> },
@@ -9688,146 +10377,96 @@ type WorkspaceZoneCard = ZoneCardRenderItem<
9688
10377
  : never
9689
10378
  : never;
9690
10379
 
9691
- type WorkspaceZone = Omit<
9692
- DreamboardUI<typeof uiContract>["Zone"],
9693
- "CardAt" | "TopCard" | "List" | "PileCards"
9694
- > & {
9695
- CardAt(
9696
- props: Omit<ZoneCardAtProps<WorkspaceZoneId>, "zone" | "children"> & {
9697
- zone?: WorkspaceZoneId;
9698
- children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9699
- },
9700
- ): ReactElement | null;
9701
- TopCard(
9702
- props: Omit<
9703
- ZoneCardAtProps<WorkspaceZoneId>,
9704
- "zone" | "index" | "children"
9705
- > & {
9706
- zone?: WorkspaceZoneId;
9707
- children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9708
- },
9709
- ): ReactElement | null;
9710
- List(
9711
- props: Omit<ZoneListProps, "children"> & {
9712
- children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9713
- },
9714
- ): ReactElement;
9715
- PileCards(
9716
- props: Omit<ZonePileCardsProps, "renderCard"> & {
9717
- renderCard: (card: WorkspaceZoneCard) => ReactNode;
9718
- },
9719
- ): ReactElement | null;
10380
+ type HandRole = "primary" | "auxiliary" | "task";
10381
+
10382
+ type WorkspaceZone = {
10383
+ useHand<const Zone extends PlayerCardZoneId>(
10384
+ name: string,
10385
+ options: { zone: Zone; role: HandRole; label: string; order?: number },
10386
+ ): HandSurface<readonly [Zone]>;
10387
+ usePile<const Zone extends CardZoneId>(
10388
+ name: string,
10389
+ options: { zone: Zone },
10390
+ ): PileSurface<readonly [Zone]>;
10391
+ useCardCollection<const Zones extends readonly CardZoneId[]>(
10392
+ name: string,
10393
+ options: { zones: Zones; mode?: "all" | "top-card" },
10394
+ ): CardCollectionSurface<Zones>;
9720
10395
  };
9721
10396
 
9722
10397
  type WorkspaceUI = Omit<
9723
10398
  DreamboardUI<typeof uiContract>,
9724
- "Root" | "Game" | "Interaction" | "Board" | "Zone"
10399
+ "Root" | "Game" | "Interaction" | "Board" | "Zone" | "Prompt" | "PromptInbox"
9725
10400
  > & {
9726
10401
  Root(props: UIRootProps): ReactElement;
9727
10402
  readonly Game: TypedGame<typeof uiContract, GameView, PlayerId, PhaseName>;
9728
- readonly Interaction: Omit<
9729
- DreamboardUI<typeof uiContract>["Interaction"],
9730
- "Switch"
9731
- > & {
9732
- Switch(props: InteractionSwitchProps): ReactElement;
10403
+ readonly Interaction: Pick<DreamboardUI<typeof uiContract>["Interaction"], "State" | "Dialog"> & {
10404
+ useForm<Key extends InteractionKey>(interaction: Key): InteractionFormSurface<Key>;
10405
+ Routes(props: InteractionRoutesProps): ReactElement;
9733
10406
  };
9734
10407
  readonly Board: WorkspaceBoard;
9735
10408
  readonly Zone: WorkspaceZone;
9736
10409
  readonly ResourceCounter: ResourceCounterComponents<ResourceId>;
9737
10410
  };
9738
10411
 
9739
- const baseUI = createDreamboardUI(uiContract);
9740
-
9741
- const resourcePresentationById = literals.resourcePresentationById as Partial<
9742
- Record<string, { label?: string; icon?: string }>
9743
- >;
10412
+ function formInputKeysForInteraction(interaction: string): Set<string> {
10413
+ const [phase, id] = interaction.split(".", 2);
10414
+ const phaseSpec = phase ? (game.phases as Record<string, unknown>)[phase] : undefined;
10415
+ const phaseRecord = phaseSpec as {
10416
+ interactions?: Record<string, { inputs?: Record<string, { kind?: string }> }>;
10417
+ cardActions?: Record<string, { inputs?: Record<string, { kind?: string }> }>;
10418
+ submit?: { inputs?: Record<string, { kind?: string }> };
10419
+ } | undefined;
10420
+ const spec =
10421
+ (id ? phaseRecord?.interactions?.[id] : undefined) ??
10422
+ (id ? phaseRecord?.cardActions?.[id] : undefined) ??
10423
+ (id === "submit" ? phaseRecord?.submit : undefined);
10424
+ return new Set(
10425
+ Object.entries((spec?.inputs ?? {}) as Record<string, { kind?: string }>)
10426
+ .filter(
10427
+ ([, collector]) =>
10428
+ collector.kind === "form" || collector.kind === "prompt",
10429
+ )
10430
+ .map(([input]) => input),
10431
+ );
10432
+ }
9744
10433
 
9745
- const resourceDisplayConfig = literals.resourceIds.map((resource) => {
9746
- const resourceId = resource as ResourceId;
9747
- const presentation = resourcePresentationById[resource as string];
9748
- return {
9749
- type: resourceId,
9750
- label: presentation?.label ?? resource,
9751
- icon: presentation?.icon ?? resource,
9752
- };
9753
- }) satisfies readonly ResourceDisplayConfig<ResourceId>[];
9754
-
9755
- const resourceCounter = createResourceCounter<ResourceId>(resourceDisplayConfig);
9756
-
9757
- export const Board: WorkspaceUI["Board"] = {
9758
- ...baseUI.Board,
9759
- HexView<
9760
- const Id extends HexBoardId,
9761
- const TSpaceView extends { id: HexBoardSpaceId<Id> },
9762
- >({ board: boardId, ...props }: HexBoardViewProps<Id, TSpaceView>) {
9763
- return createElement(baseUI.Board.HexView<HexBoardTopology<Id>, TSpaceView>, {
9764
- ...props,
9765
- board: hexStaticBoards[boardId],
9766
- });
9767
- },
9768
- HexGrid<
9769
- const Id extends HexBoardId,
9770
- const TSpaceView extends { id: HexBoardSpaceId<Id> },
9771
- >({ board: boardId, ...props }: HexBoardGridProps<Id, TSpaceView>) {
9772
- return createElement(baseUI.Board.HexGrid<HexBoardTopology<Id>, TSpaceView>, {
9773
- ...props,
9774
- board: hexStaticBoards[boardId],
9775
- });
9776
- },
9777
- } as WorkspaceUI["Board"];
9778
-
9779
- export const UI: WorkspaceUI = {
9780
- ...baseUI,
9781
- Board,
9782
- ResourceCounter: resourceCounter,
9783
- };
10434
+ export const UI = createWorkspaceUIContract<
10435
+ WorkspaceUI,
10436
+ typeof uiContract,
10437
+ ResourceId,
10438
+ WorkspaceZoneCard,
10439
+ typeof hexStaticBoards
10440
+ >({
10441
+ uiContract,
10442
+ clientParamSchemasByPhase: createClientParamSchemasByPhase(game) as ClientParamSchemaMap,
10443
+ formInputKeysForInteraction,
10444
+ resourceIds: literals.resourceIds as readonly ResourceId[],
10445
+ resourcePresentationById: literals.resourcePresentationById as Partial<
10446
+ Record<string, { label?: string; icon?: string }>
10447
+ >,
10448
+ hexStaticBoards,
10449
+ cardIdFromZoneCard: (card) => card.id,
10450
+ zoneIdFromZoneCard: (card) => card.zone,
10451
+ });
10452
+ export const Board: WorkspaceUI["Board"] = UI.Board;
10453
+ export const Zone: WorkspaceUI["Zone"] = UI.Zone;
9784
10454
  export const Game: WorkspaceUI["Game"] = UI.Game;
9785
10455
  export const Interaction = UI.Interaction;
9786
- export const Prompt = UI.Prompt;
9787
- export const PromptInbox = UI.PromptInbox;
9788
10456
  export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
9789
10457
  export const Dice: WorkspaceUI["Dice"] = UI.Dice;
9790
10458
  export const Phase = UI.Phase;
9791
- export const Zone = UI.Zone;
9792
10459
  export const ResourceCounter: WorkspaceUI["ResourceCounter"] = UI.ResourceCounter;
9793
10460
 
9794
- export type InteractionFieldRenderers<Key extends InteractionKey> =
9795
- InteractionFieldRenderMap<InteractionParamsShape<Key>>;
9796
-
9797
- export type InteractionFieldProps<
9798
- Key extends InteractionKey,
9799
- InputKey extends keyof InteractionParamsShape<Key> & string,
9800
- > = Omit<
9801
- InteractionFieldPropsGeneric<InteractionParamsShape<Key>, InputKey>,
9802
- "descriptor" | "handle"
9803
- > & {
9804
- descriptor: InteractionDescriptorFor<Key>;
9805
- handle: InteractionHandle<
9806
- InteractionParamsShape<Key>,
9807
- InteractionHandleDefaultedKeys<Key>
9808
- >;
9809
- };
9810
-
9811
10461
  export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
9812
10462
  game,
9813
10463
  ) as ClientParamSchemaMap;
9814
-
9815
- export function InteractionField<
9816
- Key extends InteractionKey,
9817
- InputKey extends keyof InteractionParamsShape<Key> & string,
9818
- >(props: InteractionFieldProps<Key, InputKey>): ReactElement | null {
9819
- return createElement(
9820
- InteractionFieldGeneric<InteractionParamsShape<Key>, InputKey>,
9821
- props,
9822
- );
9823
- }
9824
10464
  `;
9825
10465
  }
9826
10466
  function generateReducerSupportSeed() {
9827
10467
  return `import {
9828
- createReducerOps,
10468
+ createReducerEdit,
9829
10469
  createStateQueries,
9830
- pipe,
9831
10470
  } from "@dreamboard/app-sdk/reducer";
9832
10471
  import type { GameState } from "./game-contract";
9833
10472
 
@@ -9846,16 +10485,12 @@ import type { GameState } from "./game-contract";
9846
10485
  *
9847
10486
  * Recommended authoring pattern inside a phase reducer:
9848
10487
  *
9849
- * const next = pipe(
9850
- * state,
9851
- * ops.setActivePlayers([q.players.order()[0]]),
9852
- * );
9853
- * return accept(next);
10488
+ * const tx = edit(state);
10489
+ * tx.setActivePlayers([q.players.order()[0]]);
10490
+ * return accept(tx.state);
9854
10491
  */
9855
10492
 
9856
- export const ops = createReducerOps<GameState>();
9857
-
9858
- export { pipe };
10493
+ export const edit = createReducerEdit<GameState>();
9859
10494
 
9860
10495
  export function stateQueries(state: GameState) {
9861
10496
  return createStateQueries(state);
@@ -10014,18 +10649,48 @@ function isFrameworkOwnedSetupProfilesSeed(content) {
10014
10649
  }
10015
10650
  function generateSetupPhaseSeed() {
10016
10651
  return `import { z } from "zod";
10017
- import type { GameContract } from "../game-contract";
10018
- import { definePhase } from "@dreamboard/app-sdk/reducer";
10652
+ import type { GameContract, GameState } from "../game-contract";
10653
+ import {
10654
+ defineInteraction,
10655
+ definePhase,
10656
+ formInput,
10657
+ } from "@dreamboard/app-sdk/reducer";
10658
+ import { edit } from "../reducer-support";
10659
+
10660
+ const setupMoodChoices = [
10661
+ { value: "ready", label: "Ready" },
10662
+ { value: "exploring", label: "Exploring" },
10663
+ ] as const;
10664
+ type SetupMood = (typeof setupMoodChoices)[number]["value"];
10665
+ const setupPhaseStateSchema = z.object({});
10019
10666
 
10020
10667
  // A single phase file is fine while the phase is small. When a phase grows
10021
10668
  // multiple action families, move it to app/phases/<phase>/index.ts and split
10022
10669
  // interactions into neighboring concept files.
10023
10670
  export const setup = definePhase<GameContract>()({
10024
- kind: "auto",
10025
- state: z.object({}),
10671
+ kind: "player",
10672
+ state: setupPhaseStateSchema,
10026
10673
  initialState: () => ({}),
10027
- enter({ state, accept }) {
10028
- return accept(state);
10674
+ actor: ({ state }) => state.publicState.currentPlayerId,
10675
+ interactions: {
10676
+ submit: defineInteraction<GameContract, typeof setupPhaseStateSchema>()({
10677
+ inputs: {
10678
+ mood: formInput.choice<SetupMood, GameState>({
10679
+ choices: setupMoodChoices,
10680
+ defaultValue: "ready",
10681
+ }),
10682
+ },
10683
+ reduce({ state, input, accept }) {
10684
+ const tx = edit(state);
10685
+ tx.patchPublicState({
10686
+ notesByPlayerId: {
10687
+ ...state.publicState.notesByPlayerId,
10688
+ [input.playerId]: input.params.mood,
10689
+ },
10690
+ });
10691
+ return accept(tx.state);
10692
+ },
10693
+ }),
10029
10694
  },
10030
10695
  });
10031
10696
  `;
@@ -10107,55 +10772,142 @@ function generateUiFrameworkTsConfig() {
10107
10772
  `;
10108
10773
  }
10109
10774
  function generateReducerUiAppContent() {
10110
- return `import { Game, Phase, Prompt, PromptInbox } from "@dreamboard/ui-contract";
10775
+ return `import { ToastProvider } from "@dreamboard/ui-sdk";
10776
+ import { Game, Interaction, Phase, UI } from "@dreamboard/ui-contract";
10777
+ import { SetupInteractionRoutes } from "./interaction-routes";
10778
+ import { SetupScreen } from "./setup-screen";
10111
10779
 
10112
10780
  function SetupPhase() {
10781
+ const setupForm = Interaction.useForm("setup.submit");
10782
+
10113
10783
  return (
10114
- <main>
10115
- <PromptInbox.Root>
10116
- <PromptInbox.Empty>No available prompts.</PromptInbox.Empty>
10117
- <PromptInbox.Items>
10118
- {(prompt) => (
10119
- <Prompt.Root
10120
- key={prompt.interactionKey}
10121
- interaction={prompt.interactionKey}
10122
- >
10123
- <Prompt.Title />
10124
- <Prompt.Message />
10125
- <Prompt.Options>
10126
- {(option) => (
10127
- <Prompt.Option value={option.id}>
10128
- {option.label ?? option.id}
10129
- </Prompt.Option>
10130
- )}
10131
- </Prompt.Options>
10132
- </Prompt.Root>
10784
+ <>
10785
+ <SetupScreen />
10786
+ <SetupInteractionRoutes form={setupForm} />
10787
+ </>
10788
+ );
10789
+ }
10790
+
10791
+ export default function App() {
10792
+ return (
10793
+ <ToastProvider>
10794
+ <UI.Root>
10795
+ <Game.Root>
10796
+ {() => (
10797
+ <Phase.Switch
10798
+ routes={{
10799
+ setup: () => <SetupPhase />,
10800
+ }}
10801
+ />
10133
10802
  )}
10134
- </PromptInbox.Items>
10135
- </PromptInbox.Root>
10803
+ </Game.Root>
10804
+ </UI.Root>
10805
+ </ToastProvider>
10806
+ );
10807
+ }
10808
+ `;
10809
+ }
10810
+ function generateReducerUiSetupScreenContent() {
10811
+ return `import { PANEL_CLASS, SHELL_CLASS } from "./styles";
10812
+
10813
+ export function SetupScreen() {
10814
+ return (
10815
+ <main className={SHELL_CLASS}>
10816
+ <section className={PANEL_CLASS}>
10817
+ <p className="text-xs font-bold uppercase tracking-[0.14em] text-slate-500">
10818
+ Setup
10819
+ </p>
10820
+ <h1 className="text-2xl font-bold text-slate-950">Your game starts here</h1>
10821
+ <p className="max-w-prose text-sm leading-6 text-slate-600">
10822
+ Replace this screen with your board, hands, mats, and table state.
10823
+ Keep interaction wiring in the route file so visual components stay
10824
+ focused on layout and styling.
10825
+ </p>
10826
+ </section>
10136
10827
  </main>
10137
10828
  );
10138
10829
  }
10830
+ `;
10831
+ }
10832
+ function generateReducerUiInteractionRoutesContent() {
10833
+ return `import {
10834
+ Interaction,
10835
+ type InteractionFormSurface,
10836
+ } from "@dreamboard/ui-contract";
10837
+ import { ACTION_BUTTON_CLASS, PANEL_CLASS } from "./styles";
10139
10838
 
10140
- export default function App() {
10839
+ export function SetupInteractionRoutes({
10840
+ form,
10841
+ }: {
10842
+ form: InteractionFormSurface<"setup.submit">;
10843
+ }) {
10141
10844
  return (
10142
- <Game.Root>
10143
- {() => (
10144
- <Phase.Switch
10145
- routes={{
10146
- setup: () => <SetupPhase />,
10147
- }}
10148
- />
10149
- )}
10150
- </Game.Root>
10845
+ <>
10846
+ <Interaction.Routes
10847
+ routes={{
10848
+ "setup.submit": {
10849
+ collect: {
10850
+ mood: form.slot.mood,
10851
+ },
10852
+ },
10853
+ }}
10854
+ />
10855
+ <aside className={PANEL_CLASS}>
10856
+ <div className="grid gap-2">
10857
+ <p className="text-sm font-bold text-slate-950">Ready check</p>
10858
+ <form.slot.mood.Field />
10859
+ <form.Submit className={ACTION_BUTTON_CLASS}>
10860
+ Start building
10861
+ </form.Submit>
10862
+ </div>
10863
+ </aside>
10864
+ </>
10151
10865
  );
10152
10866
  }
10153
10867
  `;
10154
10868
  }
10869
+ function generateReducerUiStylesContent() {
10870
+ return `export const SHELL_CLASS =
10871
+ "min-h-screen bg-[#f8fafc] p-6 text-slate-950";
10872
+
10873
+ export const PANEL_CLASS =
10874
+ "mx-auto grid max-w-3xl gap-4 rounded-lg border border-slate-200 bg-white p-5 shadow-sm";
10875
+
10876
+ export const ACTION_BUTTON_CLASS =
10877
+ "rounded-md bg-slate-950 px-4 py-2 text-sm font-bold text-white disabled:cursor-not-allowed disabled:opacity-50";
10878
+ `;
10879
+ }
10880
+ function generateReducerUiContractTypingSmokeContent() {
10881
+ return `import { Interaction } from "@dreamboard/ui-contract";
10882
+
10883
+ export function UiContractTypingSmoke() {
10884
+ const form = Interaction.useForm("setup.submit");
10885
+
10886
+ return (
10887
+ <Interaction.Routes
10888
+ routes={{
10889
+ "setup.submit": {
10890
+ collect: {
10891
+ mood: form.slot.mood,
10892
+ },
10893
+ },
10894
+ }}
10895
+ />
10896
+ );
10897
+ }
10898
+
10899
+ // @ts-expect-error generated contracts hide legacy interaction roots.
10900
+ Interaction.Root({ interaction: "setup.submit", children: null });
10901
+ `;
10902
+ }
10155
10903
  function generateSeedFiles(manifest) {
10156
10904
  return {
10157
10905
  "app/README.md": generateAppReadmeSeed(),
10158
10906
  "ui/App.tsx": generateReducerUiAppContent(),
10907
+ "ui/interaction-routes.tsx": generateReducerUiInteractionRoutesContent(),
10908
+ "ui/setup-screen.tsx": generateReducerUiSetupScreenContent(),
10909
+ "ui/styles.ts": generateReducerUiStylesContent(),
10910
+ "ui/ui-contract-typing-smoke.tsx": generateReducerUiContractTypingSmokeContent(),
10159
10911
  "app/game-contract.ts": generateReducerGameContractSeed(),
10160
10912
  "app/game.ts": generateReducerGameSeed(),
10161
10913
  "app/setup-profiles.ts": generateSetupProfilesSeed(manifest),
@@ -10315,7 +11067,9 @@ function resolveRepoLocalPackageSource(specifier, options) {
10315
11067
  } catch {
10316
11068
  return null;
10317
11069
  }
10318
- const packageInfo = readPackageInfos(repoRoot).get(parsedSpecifier.packageName);
11070
+ const packageInfo = readPackageInfos(repoRoot).get(
11071
+ parsedSpecifier.packageName
11072
+ );
10319
11073
  if (!packageInfo) {
10320
11074
  return null;
10321
11075
  }
@@ -10723,8 +11477,10 @@ type InteractionKeyForId<Id extends InteractionId> = Extract<
10723
11477
  InteractionKey,
10724
11478
  \`\${string}.\${Id}\`
10725
11479
  >;
11480
+ type InteractionParamsForKey<Key extends InteractionKey> =
11481
+ Key extends InteractionKey ? InteractionParamsOf<Key> : never;
10726
11482
  type InteractionParamsOfId<Id extends InteractionId> =
10727
- InteractionParamsOf<InteractionKeyForId<Id>>;
11483
+ InteractionParamsForKey<InteractionKeyForId<Id>>;
10728
11484
 
10729
11485
  export interface BrowserRunnerSnapshot {
10730
11486
  sessionId: string | null;
@@ -11573,11 +12329,29 @@ function setLatestCompileAttempt(projectConfig, attempt) {
11573
12329
  function updateProjectLocalMaintainerRegistry(projectConfig, localMaintainerRegistry) {
11574
12330
  return {
11575
12331
  ...projectConfig,
11576
- localMaintainerRegistry
12332
+ localMaintainerRegistry: sanitizeProjectLocalMaintainerRegistry(
12333
+ localMaintainerRegistry
12334
+ )
12335
+ };
12336
+ }
12337
+ function sanitizeProjectLocalMaintainerRegistry(localMaintainerRegistry) {
12338
+ if (!localMaintainerRegistry?.packages.dreamboard) {
12339
+ return localMaintainerRegistry;
12340
+ }
12341
+ const { dreamboard: _dreamboard, ...packages } = localMaintainerRegistry.packages;
12342
+ return {
12343
+ ...localMaintainerRegistry,
12344
+ packages
11577
12345
  };
11578
12346
  }
11579
12347
 
11580
12348
  // src/services/project/workspace-codegen.ts
12349
+ var STARTER_UI_SEED_FILES = /* @__PURE__ */ new Set([
12350
+ "ui/interaction-routes.tsx",
12351
+ "ui/setup-screen.tsx",
12352
+ "ui/styles.ts",
12353
+ "ui/ui-contract-typing-smoke.tsx"
12354
+ ]);
11581
12355
  async function applyWorkspaceCodegen(options) {
11582
12356
  const { projectRoot, manifest } = options;
11583
12357
  const authoritativeFiles = generateAuthoritativeFiles(manifest);
@@ -11585,6 +12359,10 @@ async function applyWorkspaceCodegen(options) {
11585
12359
  const written = [];
11586
12360
  const skipped = [];
11587
12361
  const merged = [];
12362
+ const existingUiAppBeforeSeeds = await readTextFileIfExists(
12363
+ `${projectRoot}/ui/App.tsx`
12364
+ );
12365
+ const shouldWriteStarterUiSeedFiles = existingUiAppBeforeSeeds === null || existingUiAppBeforeSeeds.trim().length === 0;
11588
12366
  for (const [relativePath, content] of Object.entries(authoritativeFiles)) {
11589
12367
  const filePath = `${projectRoot}/${relativePath}`;
11590
12368
  const existingContent = await readTextFileIfExists(filePath);
@@ -11596,6 +12374,10 @@ async function applyWorkspaceCodegen(options) {
11596
12374
  for (const [relativePath, content] of Object.entries(seedFiles)) {
11597
12375
  const filePath = `${projectRoot}/${relativePath}`;
11598
12376
  const existingContent = await readTextFileIfExists(filePath);
12377
+ if (STARTER_UI_SEED_FILES.has(relativePath) && !shouldWriteStarterUiSeedFiles && existingContent === null) {
12378
+ skipped.push(relativePath);
12379
+ continue;
12380
+ }
11599
12381
  const shouldRefreshFrameworkSeed = relativePath === "app/setup-profiles.ts" && isFrameworkOwnedSetupProfilesSeed(existingContent);
11600
12382
  if (shouldRefreshFrameworkSeed) {
11601
12383
  await writeTextFile(filePath, content);
@@ -11845,7 +12627,7 @@ function buildRemoteAlignedSnapshotFiles(options) {
11845
12627
 
11846
12628
  // src/services/project/local-maintainer-registry.ts
11847
12629
  import { spawn } from "child_process";
11848
- import { existsSync as existsSync4 } from "fs";
12630
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
11849
12631
  import path10 from "path";
11850
12632
  import { fileURLToPath as fileURLToPath3 } from "url";
11851
12633
 
@@ -11884,16 +12666,34 @@ function getCliPackageRoot() {
11884
12666
  "dreamboard-cli"
11885
12667
  );
11886
12668
  } catch {
11887
- return path10.resolve(MODULE_DIR, "../../..");
12669
+ return resolveInstalledCliPackageRoot(MODULE_DIR);
12670
+ }
12671
+ }
12672
+ function resolveInstalledCliPackageRoot(moduleDir) {
12673
+ let current = moduleDir;
12674
+ while (true) {
12675
+ const packageJsonPath = path10.join(current, "package.json");
12676
+ if (existsSync4(packageJsonPath)) {
12677
+ try {
12678
+ const packageJson = JSON.parse(
12679
+ readFileSync3(packageJsonPath, "utf8")
12680
+ );
12681
+ if (packageJson.name === "dreamboard" || packageJson.name === "dreamboard-cli") {
12682
+ return current;
12683
+ }
12684
+ } catch {
12685
+ }
12686
+ }
12687
+ const parent = path10.dirname(current);
12688
+ if (parent === current) {
12689
+ return moduleDir;
12690
+ }
12691
+ current = parent;
11888
12692
  }
11889
12693
  }
11890
12694
  function getScriptInvocation() {
11891
12695
  const cliPackageRoot = getCliPackageRoot();
11892
- const scriptPath = path10.join(
11893
- cliPackageRoot,
11894
- "scripts",
11895
- "local-maintainer-registry.ts"
11896
- );
12696
+ const scriptPath = getLocalMaintainerScriptPath(cliPackageRoot);
11897
12697
  if (!existsSync4(scriptPath)) {
11898
12698
  throw new Error(
11899
12699
  [
@@ -11911,6 +12711,17 @@ function getScriptInvocation() {
11911
12711
  cwd: cliPackageRoot
11912
12712
  };
11913
12713
  }
12714
+ function getLocalMaintainerScriptPath(cliPackageRoot) {
12715
+ return path10.join(cliPackageRoot, "scripts", "local-maintainer-registry.ts");
12716
+ }
12717
+ function isInstalledCliPackageRoot(cliPackageRoot) {
12718
+ return cliPackageRoot.split(path10.sep).includes("node_modules");
12719
+ }
12720
+ function shouldSkipLocalMaintainerHelper() {
12721
+ const cliPackageRoot = getCliPackageRoot();
12722
+ const scriptPath = getLocalMaintainerScriptPath(cliPackageRoot);
12723
+ return !existsSync4(scriptPath) && isInstalledCliPackageRoot(cliPackageRoot);
12724
+ }
11914
12725
  function buildScriptSetupError(options) {
11915
12726
  return new Error(
11916
12727
  [
@@ -11922,6 +12733,15 @@ ${options.stderr.trim()}` : null
11922
12733
  ].filter(Boolean).join("\n")
11923
12734
  );
11924
12735
  }
12736
+ function parseJsonPayload(output) {
12737
+ const payload = output.trim().split(/\r?\n/).map((line) => line.trim()).reverse().find(
12738
+ (line) => line === "null" || line.startsWith("{") || line.startsWith("[")
12739
+ );
12740
+ if (!payload) {
12741
+ throw new Error("completed without returning a JSON payload");
12742
+ }
12743
+ return JSON.parse(payload);
12744
+ }
11925
12745
  async function runLocalMaintainerScript(args) {
11926
12746
  const invocation = getScriptInvocation();
11927
12747
  return new Promise((resolve, reject) => {
@@ -11975,7 +12795,7 @@ async function runLocalMaintainerScript(args) {
11975
12795
  return;
11976
12796
  }
11977
12797
  try {
11978
- resolve(JSON.parse(trimmedStdout));
12798
+ resolve(parseJsonPayload(trimmedStdout));
11979
12799
  } catch (error) {
11980
12800
  reject(
11981
12801
  buildScriptSetupError({
@@ -11992,6 +12812,9 @@ async function ensureLocalMaintainerSnapshot(apiBaseUrl) {
11992
12812
  if (!isLocalMaintainerRegistryEnabled(apiBaseUrl)) {
11993
12813
  return null;
11994
12814
  }
12815
+ if (shouldSkipLocalMaintainerHelper()) {
12816
+ return null;
12817
+ }
11995
12818
  return runLocalMaintainerScript([
11996
12819
  "ensure-snapshot",
11997
12820
  "--api-base-url",
@@ -11999,6 +12822,9 @@ async function ensureLocalMaintainerSnapshot(apiBaseUrl) {
11999
12822
  ]);
12000
12823
  }
12001
12824
  async function readWorkspaceLocalMaintainerRegistry(projectRoot, fallbackRegistryUrl = LOCAL_REGISTRY_URL) {
12825
+ if (shouldSkipLocalMaintainerHelper()) {
12826
+ return null;
12827
+ }
12002
12828
  return runLocalMaintainerScript([
12003
12829
  "read-workspace",
12004
12830
  "--project-root",
@@ -13008,6 +13834,11 @@ var config_default = defineCommand({
13008
13834
  token: {
13009
13835
  type: "string",
13010
13836
  description: "Auth token (Supabase JWT)"
13837
+ },
13838
+ scope: {
13839
+ type: "string",
13840
+ description: "Config scope: global | workspace",
13841
+ default: "global"
13011
13842
  }
13012
13843
  }
13013
13844
  },
@@ -13017,10 +13848,12 @@ var config_default = defineCommand({
13017
13848
  const globalConfig = await loadGlobalConfig();
13018
13849
  if (action === "show") {
13019
13850
  const storedSession = await getStoredSession();
13851
+ const projectRoot = await findProjectRoot(process.cwd());
13852
+ const projectConfig = projectRoot ? await loadProjectConfig(projectRoot).catch(() => void 0) : void 0;
13020
13853
  const config = resolveConfig(
13021
13854
  globalConfig,
13022
13855
  parsedArgs,
13023
- void 0,
13856
+ projectConfig,
13024
13857
  storedSession
13025
13858
  );
13026
13859
  console.log(
@@ -13033,7 +13866,9 @@ var config_default = defineCommand({
13033
13866
  } : {
13034
13867
  configPath: getGlobalConfigPath(),
13035
13868
  authPath: getGlobalAuthPath(),
13036
- environment: parsedArgs.env || globalConfig.environment || "dev",
13869
+ environment: parsedArgs.env || projectConfig?.environment || globalConfig.environment || "dev",
13870
+ workspaceConfigPath: projectRoot ? `${projectRoot}/.dreamboard/project.json` : void 0,
13871
+ workspaceEnvironment: projectConfig?.environment,
13037
13872
  apiBaseUrl: config.apiBaseUrl,
13038
13873
  webBaseUrl: config.webBaseUrl,
13039
13874
  authenticated: Boolean(config.authToken),
@@ -13051,10 +13886,24 @@ var config_default = defineCommand({
13051
13886
  "The published Dreamboard CLI does not support config overrides. Use `dreamboard login` to authenticate."
13052
13887
  );
13053
13888
  }
13054
- const updated = { ...globalConfig };
13055
- if (parsedArgs.env) updated.environment = parsedArgs.env;
13056
- await saveGlobalConfig(updated);
13057
- const overrideToken = valueOrUndefined(parsedArgs.token);
13889
+ if (parsedArgs.scope === "workspace") {
13890
+ const projectRoot = await findProjectRoot(process.cwd());
13891
+ if (!projectRoot) {
13892
+ throw new Error(
13893
+ "Workspace-scoped config requires a Dreamboard project (.dreamboard/project.json)."
13894
+ );
13895
+ }
13896
+ const projectConfig = await loadProjectConfig(projectRoot);
13897
+ await updateProjectState(projectRoot, {
13898
+ ...projectConfig,
13899
+ ...parsedArgs.env ? { environment: parsedArgs.env } : {}
13900
+ });
13901
+ } else {
13902
+ const updated = { ...globalConfig };
13903
+ if (parsedArgs.env) updated.environment = parsedArgs.env;
13904
+ await saveGlobalConfig(updated);
13905
+ }
13906
+ const overrideToken = valueOrUndefined2(parsedArgs.token);
13058
13907
  if (overrideToken) {
13059
13908
  await setAccessOnlySession(overrideToken);
13060
13909
  }
@@ -13068,7 +13917,7 @@ var config_default = defineCommand({
13068
13917
  });
13069
13918
 
13070
13919
  // src/commands/dev.ts
13071
- import path24 from "path";
13920
+ import path25 from "path";
13072
13921
  import { createHash as createHash3 } from "crypto";
13073
13922
 
13074
13923
  // ../../packages/ui-host-runtime/src/actor-principal.ts
@@ -13540,11 +14389,32 @@ async function resolveDevBearer(config) {
13540
14389
  case "unchanged":
13541
14390
  case "rotated":
13542
14391
  return { kind: "ok", token: result.credentials.accessToken };
13543
- case "transient_failure":
14392
+ case "transient_failure": {
14393
+ const expiry = getAccessTokenExpiry(config.authToken);
14394
+ const isExpired = expiry !== null && expiry.getTime() <= Date.now();
14395
+ if (isExpired) {
14396
+ if (shouldRepairLocalDevSession(config)) {
14397
+ const repaired = await repairLocalDevSession();
14398
+ if (repaired) {
14399
+ return { kind: "ok", token: repaired.accessToken };
14400
+ }
14401
+ }
14402
+ return {
14403
+ kind: "permanent_invalid",
14404
+ message: `Access token refresh failed: ${result.message}. Run \`dreamboard login\` to authenticate again.`
14405
+ };
14406
+ }
13544
14407
  return { kind: "ok", token: config.authToken ?? null };
14408
+ }
13545
14409
  }
13546
14410
  } catch (err) {
13547
14411
  if (err instanceof PermanentRefreshError) {
14412
+ if (shouldRepairLocalDevSession(config)) {
14413
+ const repaired = await repairLocalDevSession();
14414
+ if (repaired) {
14415
+ return { kind: "ok", token: repaired.accessToken };
14416
+ }
14417
+ }
13548
14418
  return { kind: "permanent_invalid", message: err.message };
13549
14419
  }
13550
14420
  throw err;
@@ -14227,7 +15097,7 @@ function extractQueryParam(req, name) {
14227
15097
  );
14228
15098
  return value?.trim() || null;
14229
15099
  }
14230
- function appendQuery(path28, query) {
15100
+ function appendQuery(path29, query) {
14231
15101
  const params = new URLSearchParams();
14232
15102
  for (const [key, value] of Object.entries(query)) {
14233
15103
  if (value) {
@@ -14235,7 +15105,7 @@ function appendQuery(path28, query) {
14235
15105
  }
14236
15106
  }
14237
15107
  const serialized = params.toString();
14238
- return serialized ? `${path28}?${serialized}` : path28;
15108
+ return serialized ? `${path29}?${serialized}` : path29;
14239
15109
  }
14240
15110
  async function loadCurrentSession(options) {
14241
15111
  if (!await exists(options.sessionFilePath)) {
@@ -14254,12 +15124,12 @@ async function persistSessionId(sessionFilePath, sessionId) {
14254
15124
  createPersistedDevSession({ sessionId })
14255
15125
  );
14256
15126
  }
14257
- async function fetchBackendJson(config, path28, options = {}) {
15127
+ async function fetchBackendJson(config, path29, options = {}) {
14258
15128
  const bearer = await resolveDevBearer(config);
14259
15129
  if (bearer.kind === "permanent_invalid") {
14260
15130
  throw new HttpError(401, bearer.message);
14261
15131
  }
14262
- const response = await fetch(`${config.apiBaseUrl}${path28}`, {
15132
+ const response = await fetch(`${config.apiBaseUrl}${path29}`, {
14263
15133
  method: options.method ?? "GET",
14264
15134
  headers: {
14265
15135
  ...bearer.token ? { authorization: `Bearer ${bearer.token}` } : {},
@@ -14401,6 +15271,37 @@ function prepareFallbackStylesheet(options) {
14401
15271
  return generatedPath;
14402
15272
  }
14403
15273
 
15274
+ // src/dev-host/dev-hmr-guard-plugin.ts
15275
+ import path18 from "path";
15276
+ function createDevHmrGuardPlugin(options) {
15277
+ return {
15278
+ name: "dreamboard-dev-hmr-guard",
15279
+ handleHotUpdate(context) {
15280
+ if (shouldHandleProjectHotUpdate(options.projectRoot, context.file)) {
15281
+ return void 0;
15282
+ }
15283
+ context.server.config.logger.info(
15284
+ `[dreamboard] ignored HMR outside project: ${path18.relative(
15285
+ options.projectRoot,
15286
+ context.file
15287
+ )}`
15288
+ );
15289
+ return [];
15290
+ }
15291
+ };
15292
+ }
15293
+ function shouldHandleProjectHotUpdate(projectRoot, file) {
15294
+ const normalizedProjectRoot = path18.resolve(projectRoot);
15295
+ const normalizedFile = path18.resolve(file);
15296
+ const relativePath = path18.relative(normalizedProjectRoot, normalizedFile);
15297
+ const isInsideProject = relativePath === "" || relativePath.length > 0 && !relativePath.startsWith("..") && !path18.isAbsolute(relativePath);
15298
+ if (!isInsideProject) {
15299
+ return false;
15300
+ }
15301
+ const pathSegments = relativePath.split(path18.sep);
15302
+ return !pathSegments.includes(".dreamboard") && !pathSegments.includes("node_modules");
15303
+ }
15304
+
14404
15305
  // src/dev-host/start-dev-server.ts
14405
15306
  var require2 = createRequire2(import.meta.url);
14406
15307
  var tailwindCssEntry = require2.resolve("tailwindcss/index.css");
@@ -14423,6 +15324,7 @@ async function startDreamboardDevServer(options) {
14423
15324
  root: platform2.devHostRoot,
14424
15325
  appType: "spa",
14425
15326
  plugins: [
15327
+ createDevHmrGuardPlugin({ projectRoot }),
14426
15328
  react(),
14427
15329
  createVirtualDevModulesPlugin({
14428
15330
  projectRoot,
@@ -14493,11 +15395,11 @@ function getBoundPort(server) {
14493
15395
  }
14494
15396
 
14495
15397
  // src/services/testing/reducer-native-test-harness.ts
14496
- import path21 from "path";
15398
+ import path22 from "path";
14497
15399
  import {
14498
15400
  existsSync as existsSync8,
14499
15401
  mkdirSync as mkdirSync2,
14500
- readFileSync as readFileSync3,
15402
+ readFileSync as readFileSync4,
14501
15403
  rmSync as rmSync2,
14502
15404
  writeFileSync as writeFileSync2
14503
15405
  } from "fs";
@@ -14606,6 +15508,9 @@ var ProjectSeatsDynamicRequestSchema = external_exports.object({ "state": Reduce
14606
15508
  var BoardStaticProjectionSchema = external_exports.object({ "view": JsonValueSchema, "hash": external_exports.string().min(1), "manifestVersion": external_exports.string() }).strict();
14607
15509
 
14608
15510
  // ../../packages/app-sdk/src/reducer/per-player.ts
15511
+ function asPlayerId(raw) {
15512
+ return raw;
15513
+ }
14609
15514
  function perPlayerGet(value, id) {
14610
15515
  for (const [candidate, v] of value.entries) {
14611
15516
  if (candidate === id) {
@@ -14727,6 +15632,33 @@ function resolveDependentDomainChoices(choices, context) {
14727
15632
  disabledReason: choice.disabledReason
14728
15633
  }));
14729
15634
  }
15635
+ function choiceValuesInclude(choices, value) {
15636
+ return choices.some((choice) => Object.is(choice.value, value));
15637
+ }
15638
+ function assertChoiceDefaultInChoices(inputName, choices, defaultValue) {
15639
+ if (choiceValuesInclude(choices, defaultValue)) return;
15640
+ const printable = defaultValue === null ? "null" : `'${defaultValue}'`;
15641
+ throw new Error(
15642
+ `${inputName} defaultValue ${printable} must be one of its choices. If null is a valid value, add an explicit { value: null, label: ... } choice.`
15643
+ );
15644
+ }
15645
+ function choiceSchema(choices) {
15646
+ const values = choices.map((choice) => choice.value);
15647
+ const stringValues = values.filter(
15648
+ (value) => value !== null
15649
+ );
15650
+ const hasNull = values.some((value) => value === null);
15651
+ if (stringValues.length === 0) {
15652
+ return external_exports.null();
15653
+ }
15654
+ const stringSchema = external_exports.enum(
15655
+ stringValues
15656
+ );
15657
+ if (hasNull) {
15658
+ return external_exports.union([stringSchema, external_exports.null()]);
15659
+ }
15660
+ return stringSchema;
15661
+ }
14730
15662
  function resolveResourceMapChoices(choices, context) {
14731
15663
  const resources = context.state.table.resources;
14732
15664
  const firstResourceMap = isPerPlayer(resources) && resources.entries.length > 0 ? resources.entries[0]?.[1] : resources;
@@ -14810,38 +15742,76 @@ function choiceInput(options) {
14810
15742
  if (Array.isArray(options.choices) && options.choices.length === 0) {
14811
15743
  throw new Error("formInput.choice requires at least one choice.");
14812
15744
  }
14813
- const schema = Array.isArray(options.choices) ? external_exports.enum(
14814
- options.choices.map((choice) => choice.value)
14815
- ) : external_exports.string();
15745
+ const staticChoices = Array.isArray(options.choices) ? options.choices : null;
15746
+ const hasStaticDefault = typeof options.defaultValue !== "function";
15747
+ const staticDefault = options.defaultValue;
15748
+ if (staticChoices && hasStaticDefault) {
15749
+ assertChoiceDefaultInChoices(
15750
+ "formInput.choice",
15751
+ staticChoices,
15752
+ staticDefault
15753
+ );
15754
+ }
15755
+ const schema = staticChoices ? choiceSchema(staticChoices) : external_exports.string().nullable();
15756
+ const dynamicDefault = typeof options.defaultValue === "function" ? options.defaultValue : void 0;
14816
15757
  return {
14817
15758
  kind: "form",
14818
15759
  schema,
14819
- ..."defaultValue" in options ? { defaultValue: options.defaultValue } : {},
15760
+ ...hasStaticDefault ? { defaultValue: staticDefault } : {},
14820
15761
  ...dependsOn ? { dependsOn } : {},
14821
15762
  domain: (state, playerId, q, derived, values) => {
15763
+ const context = {
15764
+ state,
15765
+ playerId,
15766
+ q,
15767
+ derived
15768
+ };
15769
+ const choices = dependsOn ? resolveDependentDomainChoices(
15770
+ options.choices,
15771
+ {
15772
+ ...context,
15773
+ values: values ?? {}
15774
+ }
15775
+ ) : resolveDomainChoices(options.choices, {
15776
+ ...context,
15777
+ values: {}
15778
+ });
15779
+ if (hasStaticDefault) {
15780
+ assertChoiceDefaultInChoices(
15781
+ "formInput.choice",
15782
+ choices,
15783
+ staticDefault
15784
+ );
15785
+ }
14822
15786
  return {
14823
15787
  type: "choice",
14824
- choices: dependsOn ? resolveDependentDomainChoices(
14825
- options.choices,
14826
- {
14827
- state,
14828
- playerId,
14829
- q,
14830
- derived,
14831
- values: values ?? {}
14832
- }
14833
- ) : resolveDomainChoices(
14834
- options.choices,
14835
- {
14836
- state,
14837
- playerId,
14838
- q,
14839
- derived,
14840
- values: {}
14841
- }
14842
- )
15788
+ choices
14843
15789
  };
14844
- }
15790
+ },
15791
+ ...dynamicDefault ? {
15792
+ resolveDefaultValue: (state, playerId, q, derived, domain) => {
15793
+ const choices = domain.type === "choice" ? domain.choices.map((choice) => ({
15794
+ value: choice.value,
15795
+ label: choice.label,
15796
+ icon: choice.icon,
15797
+ badge: choice.badge,
15798
+ description: choice.description,
15799
+ disabled: choice.disabled,
15800
+ disabledReason: choice.disabledReason
15801
+ })) : [];
15802
+ const resolved = dynamicDefault({
15803
+ state,
15804
+ playerId,
15805
+ q,
15806
+ derived,
15807
+ values: {},
15808
+ choices
15809
+ });
15810
+ if (resolved === void 0) return void 0;
15811
+ assertChoiceDefaultInChoices("formInput.choice", choices, resolved);
15812
+ return resolved;
15813
+ }
15814
+ } : {}
14845
15815
  };
14846
15816
  }
14847
15817
  function choiceListInput(options) {
@@ -14862,7 +15832,12 @@ function choiceListInput(options) {
14862
15832
  derived,
14863
15833
  values: {}
14864
15834
  };
14865
- const choices = resolveDomainChoices(options.choices, context);
15835
+ const choices = resolveDomainChoices(options.choices, context).map(
15836
+ (choice) => ({
15837
+ ...choice,
15838
+ value: choice.value
15839
+ })
15840
+ );
14866
15841
  return {
14867
15842
  type: "choiceList",
14868
15843
  choices,
@@ -14944,17 +15919,19 @@ function makeBoardCollector(kind) {
14944
15919
  targetId
14945
15920
  )),
14946
15921
  ...dependsOn ? { dependsOn } : {},
14947
- domain: dependsOn ? (state, playerId, q, _derived, values) => ({
14948
- type: "target",
15922
+ domain: (state, playerId, q, _derived, values) => ({
15923
+ type: "boardTarget",
15924
+ projection: "resolved",
14949
15925
  targetKind: target.targetKind,
14950
15926
  boardId: target.boardId,
15927
+ valueKind: target.valueKind,
14951
15928
  eligibleTargets: target.eligible({
14952
15929
  state,
14953
15930
  playerId,
14954
15931
  q,
14955
15932
  values
14956
15933
  }).map(String)
14957
- }) : void 0,
15934
+ }),
14958
15935
  meta: {
14959
15936
  targetKind: target.targetKind,
14960
15937
  boardId: target.boardId,
@@ -15129,8 +16106,8 @@ function createCardTargetBuilder(zoneIds) {
15129
16106
  }
15130
16107
  }
15131
16108
  ),
15132
- zoneIds: [...zoneIds],
15133
- zoneId: zoneIds[0] ?? "",
16109
+ zoneIds,
16110
+ zoneId: zoneIds[0],
15134
16111
  targetKind: "card"
15135
16112
  })
15136
16113
  );
@@ -15164,10 +16141,10 @@ function cardInput(options) {
15164
16141
  targetId
15165
16142
  )),
15166
16143
  ...dependsOn ? { dependsOn } : {},
15167
- domain: dependsOn ? (state, playerId, q, _derived, values) => ({
15168
- type: "target",
16144
+ domain: (state, playerId, q, _derived, values) => ({
16145
+ type: "cardTarget",
16146
+ projection: "resolved",
15169
16147
  targetKind: target.targetKind,
15170
- zoneId: target.zoneId,
15171
16148
  zoneIds: target.zoneIds,
15172
16149
  eligibleTargets: target.eligible({
15173
16150
  state,
@@ -15175,7 +16152,7 @@ function cardInput(options) {
15175
16152
  q,
15176
16153
  values
15177
16154
  }).map(String)
15178
- }) : void 0,
16155
+ }),
15179
16156
  meta: {
15180
16157
  zoneId: target.zoneId,
15181
16158
  zoneIds: target.zoneIds,
@@ -17344,6 +18321,126 @@ function createReducerFx(_state) {
17344
18321
  function updateTable(state, nextTable) {
17345
18322
  return { ...state, table: nextTable };
17346
18323
  }
18324
+ function computePlayerZoneVisibility(table, zoneId, playerId) {
18325
+ const mode = table.handVisibility[zoneId];
18326
+ if (mode === "all" || mode === "public") {
18327
+ return { faceUp: true };
18328
+ }
18329
+ return { faceUp: false, visibleTo: [playerId] };
18330
+ }
18331
+ function readPlayerZoneCards(table, zoneId, playerId) {
18332
+ const zone = table.zones.perPlayer[zoneId] ?? table.hands[zoneId];
18333
+ if (!zone) {
18334
+ throw new Error(`Player zone '${zoneId}' does not exist.`);
18335
+ }
18336
+ return perPlayerGet(zone, asPlayerId(playerId)) ?? [];
18337
+ }
18338
+ function writePlayerZoneCards(table, zoneId, playerId, cards) {
18339
+ const currentZone = table.zones.perPlayer[zoneId];
18340
+ const currentHand = table.hands[zoneId];
18341
+ const player = asPlayerId(playerId);
18342
+ if (currentZone) {
18343
+ table.zones.perPlayer[zoneId] = perPlayerSet(
18344
+ currentZone,
18345
+ player,
18346
+ [...cards]
18347
+ );
18348
+ }
18349
+ if (currentHand) {
18350
+ table.hands[zoneId] = perPlayerSet(
18351
+ currentHand,
18352
+ player,
18353
+ [...cards]
18354
+ );
18355
+ }
18356
+ }
18357
+ function assertCardAllowedInPlayerZone(table, zoneId, cardId) {
18358
+ const allowedCardSetIds = table.zones.cardSetIdsByZoneId?.[zoneId];
18359
+ if (!allowedCardSetIds || allowedCardSetIds.length === 0) {
18360
+ return;
18361
+ }
18362
+ const card = table.cards[cardId];
18363
+ if (!card) {
18364
+ throw new Error(`Card '${cardId}' does not exist.`);
18365
+ }
18366
+ if (!allowedCardSetIds.includes(card.cardSetId)) {
18367
+ throw new Error(
18368
+ `Card '${cardId}' from set '${card.cardSetId}' is not allowed in player zone '${zoneId}'.`
18369
+ );
18370
+ }
18371
+ }
18372
+ function rotatePlayerZoneTable(options) {
18373
+ const nextTable = cloneRuntimeTable(options.table);
18374
+ const zoneId = options.zoneId;
18375
+ if (!nextTable.zones.perPlayer[zoneId] && !nextTable.hands[zoneId]) {
18376
+ throw new Error(`Player zone '${zoneId}' does not exist.`);
18377
+ }
18378
+ const players = [...options.players ?? nextTable.playerOrder];
18379
+ if (players.length === 0) {
18380
+ return nextTable;
18381
+ }
18382
+ const playerSet = new Set(nextTable.playerOrder);
18383
+ for (const playerId of players) {
18384
+ if (!playerSet.has(playerId)) {
18385
+ throw new Error(
18386
+ `Cannot rotate player zone '${zoneId}': player '${playerId}' is not in player order.`
18387
+ );
18388
+ }
18389
+ }
18390
+ const selectedByPlayer = /* @__PURE__ */ new Map();
18391
+ for (const playerId of players) {
18392
+ const sourceCards = readPlayerZoneCards(nextTable, zoneId, playerId);
18393
+ const selected = options.cardIdsByPlayer?.[playerId] ?? sourceCards;
18394
+ for (const cardId of selected) {
18395
+ if (!sourceCards.includes(cardId)) {
18396
+ throw new Error(
18397
+ `Cannot rotate player zone '${zoneId}': card '${cardId}' is not in zone for player '${playerId}'.`
18398
+ );
18399
+ }
18400
+ assertCardAllowedInPlayerZone(nextTable, zoneId, cardId);
18401
+ }
18402
+ selectedByPlayer.set(playerId, [...selected]);
18403
+ }
18404
+ const removeByPlayer = /* @__PURE__ */ new Map();
18405
+ for (const playerId of players) {
18406
+ const selected = new Set(selectedByPlayer.get(playerId) ?? []);
18407
+ removeByPlayer.set(
18408
+ playerId,
18409
+ readPlayerZoneCards(nextTable, zoneId, playerId).filter(
18410
+ (cardId) => !selected.has(cardId)
18411
+ )
18412
+ );
18413
+ }
18414
+ const additionsByPlayer = new Map(
18415
+ players.map((playerId) => [playerId, []])
18416
+ );
18417
+ for (const [index, fromPlayerId] of players.entries()) {
18418
+ const offset = options.direction === "left" ? 1 : -1;
18419
+ const recipient = players[(index + offset + players.length) % players.length];
18420
+ additionsByPlayer.get(recipient).push(...selectedByPlayer.get(fromPlayerId) ?? []);
18421
+ }
18422
+ for (const playerId of players) {
18423
+ const remaining = removeByPlayer.get(playerId) ?? [];
18424
+ const additions = additionsByPlayer.get(playerId) ?? [];
18425
+ const nextCards = options.position === "top" ? [...additions, ...remaining] : [...remaining, ...additions];
18426
+ writePlayerZoneCards(nextTable, zoneId, playerId, nextCards);
18427
+ for (const [position, cardId] of nextCards.entries()) {
18428
+ nextTable.componentLocations[cardId] = {
18429
+ type: "InHand",
18430
+ handId: zoneId,
18431
+ playerId,
18432
+ position
18433
+ };
18434
+ nextTable.ownerOfCard[cardId] = playerId;
18435
+ nextTable.visibility[cardId] = computePlayerZoneVisibility(
18436
+ nextTable,
18437
+ zoneId,
18438
+ playerId
18439
+ );
18440
+ }
18441
+ }
18442
+ return nextTable;
18443
+ }
17347
18444
  var moveComponentToEdgeInternal = moveComponentToEdge;
17348
18445
  var moveComponentToVertexInternal = moveComponentToVertex;
17349
18446
  var dealCardsFromDeckToHandInternal = dealCardsFromDeckToHand;
@@ -17527,6 +18624,19 @@ function createReducerOps() {
17527
18624
  return updateTable(state, nextTable);
17528
18625
  };
17529
18626
  },
18627
+ rotatePlayerZone(args) {
18628
+ return (state) => {
18629
+ const nextTable = rotatePlayerZoneTable({
18630
+ table: state.table,
18631
+ zoneId: args.zoneId,
18632
+ direction: args.direction,
18633
+ players: args.players,
18634
+ cardIdsByPlayer: args.cardIdsByPlayer,
18635
+ position: args.position ?? "bottom"
18636
+ });
18637
+ return updateTable(state, nextTable);
18638
+ };
18639
+ },
17530
18640
  moveComponentToSpace(args) {
17531
18641
  return (state) => {
17532
18642
  const nextTable = moveComponentToSpace(
@@ -17626,6 +18736,47 @@ function createReducerOps() {
17626
18736
  return impl;
17627
18737
  }
17628
18738
 
18739
+ // ../../packages/app-sdk/src/reducer/transaction.ts
18740
+ function createReducerTransaction(initialState, ops = createReducerOps()) {
18741
+ let currentState = initialState;
18742
+ let currentQueries = createStateQueries(currentState);
18743
+ const refresh = (nextState) => {
18744
+ currentState = nextState;
18745
+ currentQueries = createStateQueries(currentState);
18746
+ return currentState;
18747
+ };
18748
+ const apply = (op) => refresh(op(currentState));
18749
+ const base = {
18750
+ get state() {
18751
+ return currentState;
18752
+ },
18753
+ get q() {
18754
+ return currentQueries;
18755
+ },
18756
+ apply
18757
+ };
18758
+ return new Proxy(base, {
18759
+ get(target, property, receiver) {
18760
+ if (property in target) {
18761
+ return Reflect.get(target, property, receiver);
18762
+ }
18763
+ const opFactory = ops[property];
18764
+ if (typeof opFactory !== "function") {
18765
+ return void 0;
18766
+ }
18767
+ return (...args) => {
18768
+ const op = opFactory(
18769
+ ...args
18770
+ );
18771
+ return apply(op);
18772
+ };
18773
+ }
18774
+ });
18775
+ }
18776
+ function createReducerEdit(ops = createReducerOps()) {
18777
+ return (state) => createReducerTransaction(state, ops);
18778
+ }
18779
+
17629
18780
  // ../../packages/app-sdk/src/reducer/definition-index.ts
17630
18781
  function phaseEntriesOf(definition) {
17631
18782
  return Object.entries(definition.phases);
@@ -17927,7 +19078,8 @@ function createTrustedRuntimeScope(definition) {
17927
19078
  }
17928
19079
  const helpers = {
17929
19080
  ...runtimeResultHelpers,
17930
- ops: createReducerOps()
19081
+ ops: createReducerOps(),
19082
+ edit: createReducerEdit()
17931
19083
  };
17932
19084
  function fxForState2(state) {
17933
19085
  return fxForState(state);
@@ -18264,10 +19416,10 @@ function createTrustedInstructionRunner(scope, interactions, lifecycle) {
18264
19416
 
18265
19417
  // ../../packages/app-sdk/src/reducer/parse-utils.ts
18266
19418
  function formatIssue(label, issue) {
18267
- const path28 = issue.path.map(
19419
+ const path29 = issue.path.map(
18268
19420
  (segment) => typeof segment === "symbol" ? segment.toString() : String(segment)
18269
19421
  ).join(".");
18270
- return `${path28 || label}: ${issue.message}`;
19422
+ return `${path29 || label}: ${issue.message}`;
18271
19423
  }
18272
19424
  function safeParseOrThrow(schema, value, label) {
18273
19425
  const parsed = schema.safeParse(value);
@@ -18288,6 +19440,7 @@ function makeValidationError(errorCode, message) {
18288
19440
  }
18289
19441
 
18290
19442
  // ../../packages/app-sdk/src/reducer/bundle/trusted/interaction-collectors.ts
19443
+ var MAX_EAGER_DEPENDENT_DOMAIN_CASES = 64;
18291
19444
  function collectTargetDomainMetadata(collector) {
18292
19445
  switch (collector.kind) {
18293
19446
  case "card": {
@@ -18305,28 +19458,63 @@ function collectTargetDomainMetadata(collector) {
18305
19458
  const meta = collector.meta ?? {};
18306
19459
  return {
18307
19460
  targetKind: meta.targetKind ?? collector.kind.replace("board-", ""),
18308
- boardId: meta.boardId
19461
+ boardId: meta.boardId,
19462
+ valueKind: meta.valueKind
18309
19463
  };
18310
19464
  }
18311
19465
  default:
18312
19466
  return {};
18313
19467
  }
18314
19468
  }
18315
- function isTargetCollector(collector) {
18316
- return collector.kind === "card" || collector.kind === "board-edge" || collector.kind === "board-space" || collector.kind === "board-tile" || collector.kind === "board-vertex";
18317
- }
18318
19469
  function withSelection(domain, collector) {
18319
19470
  if (!collector.selection) return domain;
18320
19471
  return { ...domain, selection: collector.selection };
18321
19472
  }
19473
+ function lazyTargetDomainFromCollectorMetadata(key, collector, dependencies) {
19474
+ const metadata = collectTargetDomainMetadata(collector);
19475
+ if (collector.kind === "card") {
19476
+ return withSelection(
19477
+ {
19478
+ type: "cardTarget",
19479
+ projection: "lazy",
19480
+ targetKind: "card",
19481
+ zoneIds: metadata.zoneIds ?? (metadata.zoneId === void 0 ? [] : [metadata.zoneId]),
19482
+ dependencies: {
19483
+ mode: "lazy",
19484
+ dependsOn: dependencies,
19485
+ resolver: { inputKey: key }
19486
+ }
19487
+ },
19488
+ collector
19489
+ );
19490
+ }
19491
+ if (collector.kind === "board-edge" || collector.kind === "board-space" || collector.kind === "board-tile" || collector.kind === "board-vertex") {
19492
+ return withSelection(
19493
+ {
19494
+ type: "boardTarget",
19495
+ projection: "lazy",
19496
+ targetKind: metadata.targetKind ?? collector.kind.replace("board-", ""),
19497
+ boardId: metadata.boardId ?? "",
19498
+ ...metadata.valueKind ? { valueKind: metadata.valueKind } : {},
19499
+ dependencies: {
19500
+ mode: "lazy",
19501
+ dependsOn: dependencies,
19502
+ resolver: { inputKey: key }
19503
+ }
19504
+ },
19505
+ collector
19506
+ );
19507
+ }
19508
+ return null;
19509
+ }
18322
19510
  function finiteValuesForDependency(key, domain) {
18323
19511
  if (!domain) {
18324
19512
  throw new Error(
18325
19513
  `Interaction input '${key}' is declared as a dependency before it has a projected domain.`
18326
19514
  );
18327
19515
  }
18328
- if (domain.type === "target") {
18329
- if (!domain.eligibleTargets) {
19516
+ if (domain.type === "cardTarget" || domain.type === "boardTarget") {
19517
+ if (domain.projection !== "resolved") {
18330
19518
  throw new Error(
18331
19519
  `Interaction input '${key}' cannot be used as a dependency because its target domain is not finite.`
18332
19520
  );
@@ -18334,7 +19522,9 @@ function finiteValuesForDependency(key, domain) {
18334
19522
  return [...domain.eligibleTargets];
18335
19523
  }
18336
19524
  if (domain.type === "choice") {
18337
- return domain.choices.map((choice) => choice.value);
19525
+ return domain.choices.flatMap(
19526
+ (choice) => choice.value === null ? [] : [choice.value]
19527
+ );
18338
19528
  }
18339
19529
  throw new Error(
18340
19530
  `Interaction input '${key}' cannot be used as a dependency. V1 supports finite target and choice dependencies only.`
@@ -18352,13 +19542,44 @@ function cartesianDependencyTuples(dependencies, domainsByKey) {
18352
19542
  );
18353
19543
  }
18354
19544
  function withoutDependentProjection(domain) {
18355
- const {
18356
- dependsOn: _dependsOn,
18357
- dependentCases: _dependentCases,
18358
- ...rest
18359
- } = domain;
19545
+ const { dependencies: _dependencies, ...rest } = domain;
18360
19546
  return rest;
18361
19547
  }
19548
+ function toLazyTargetDomain(key, domain, dependencies) {
19549
+ if (domain.type === "cardTarget") {
19550
+ return {
19551
+ type: "cardTarget",
19552
+ projection: "lazy",
19553
+ targetKind: "card",
19554
+ zoneIds: domain.zoneIds,
19555
+ ...domain.selection ? { selection: domain.selection } : {},
19556
+ dependencies: {
19557
+ mode: "lazy",
19558
+ dependsOn: dependencies,
19559
+ resolver: { inputKey: key }
19560
+ }
19561
+ };
19562
+ }
19563
+ if (domain.type === "boardTarget") {
19564
+ return {
19565
+ type: "boardTarget",
19566
+ projection: "lazy",
19567
+ targetKind: domain.targetKind,
19568
+ boardId: domain.boardId,
19569
+ ...domain.valueKind ? { valueKind: domain.valueKind } : {},
19570
+ ...domain.selection ? { selection: domain.selection } : {},
19571
+ dependencies: {
19572
+ mode: "lazy",
19573
+ dependsOn: dependencies,
19574
+ resolver: { inputKey: key }
19575
+ }
19576
+ };
19577
+ }
19578
+ return null;
19579
+ }
19580
+ function dependencyCaseCount(dependencies, domainsByKey) {
19581
+ return dependencies.map((key) => finiteValuesForDependency(key, domainsByKey[key]).length).reduce((total, count) => total * count, 1);
19582
+ }
18362
19583
  function unsupportedDefaultInputError(key, collector) {
18363
19584
  return new Error(
18364
19585
  `Interaction input '${key}' uses a '${collector.kind}' collector without a default-renderable domain or target metadata. Use formInput.choice(...), formInput.choiceList(...), formInput.number(...), formInput.resourceMap(...), cardInput(...), or boardInput(...). For custom payloads, use a custom interaction surface with paramsSchema instead of the default InteractionForm.`
@@ -18437,9 +19658,21 @@ function collectInputDomains(interaction, domainState, playerId, eligibleTargets
18437
19658
  continue;
18438
19659
  }
18439
19660
  if (collector.domain) {
19661
+ const domainProjector = collector.domain;
18440
19662
  const dependencies = collector.dependsOn ?? [];
19663
+ if (options.includeEligibleTargets === false && !Object.prototype.hasOwnProperty.call(eligibleTargets, key)) {
19664
+ const lazyDomain2 = lazyTargetDomainFromCollectorMetadata(
19665
+ key,
19666
+ collector,
19667
+ dependencies
19668
+ );
19669
+ if (lazyDomain2) {
19670
+ result[key] = lazyDomain2;
19671
+ continue;
19672
+ }
19673
+ }
18441
19674
  const baseDomain = withSelection(
18442
- collector.domain(
19675
+ domainProjector(
18443
19676
  domainState,
18444
19677
  playerId,
18445
19678
  queries(),
@@ -18452,6 +19685,12 @@ function collectInputDomains(interaction, domainState, playerId, eligibleTargets
18452
19685
  result[key] = baseDomain;
18453
19686
  continue;
18454
19687
  }
19688
+ const caseCount = dependencyCaseCount(dependencies, result);
19689
+ const lazyDomain = caseCount > MAX_EAGER_DEPENDENT_DOMAIN_CASES ? toLazyTargetDomain(key, baseDomain, dependencies) : null;
19690
+ if (lazyDomain) {
19691
+ result[key] = lazyDomain;
19692
+ continue;
19693
+ }
18455
19694
  const dependentCases = cartesianDependencyTuples(
18456
19695
  dependencies,
18457
19696
  result
@@ -18459,7 +19698,7 @@ function collectInputDomains(interaction, domainState, playerId, eligibleTargets
18459
19698
  when: values,
18460
19699
  domain: withoutDependentProjection(
18461
19700
  withSelection(
18462
- collector.domain?.(
19701
+ domainProjector(
18463
19702
  domainState,
18464
19703
  playerId,
18465
19704
  queries(),
@@ -18472,24 +19711,13 @@ function collectInputDomains(interaction, domainState, playerId, eligibleTargets
18472
19711
  }));
18473
19712
  result[key] = {
18474
19713
  ...baseDomain,
18475
- dependsOn: dependencies,
18476
- dependentCases
19714
+ dependencies: {
19715
+ mode: "eager",
19716
+ dependentCases
19717
+ }
18477
19718
  };
18478
19719
  continue;
18479
19720
  }
18480
- const targets = eligibleTargets[key];
18481
- const targetMetadata = collectTargetDomainMetadata(collector);
18482
- if (isTargetCollector(collector) || Boolean(collector.eligibleTargets) || Boolean(collector.validateTarget) || targets || Object.keys(targetMetadata).length > 0) {
18483
- result[key] = withSelection(
18484
- {
18485
- type: "target",
18486
- ...targetMetadata,
18487
- ...targets ? { eligibleTargets: targets } : {}
18488
- },
18489
- collector
18490
- );
18491
- continue;
18492
- }
18493
19721
  throw unsupportedDefaultInputError(key, collector);
18494
19722
  }
18495
19723
  return result;
@@ -18538,16 +19766,50 @@ function collectInteractionInputs(interaction, domainState, playerId, options =
18538
19766
  derived(),
18539
19767
  domain
18540
19768
  );
19769
+ const defaultValue = "defaultValue" in collector ? collector.defaultValue : dynamicDefault;
19770
+ warnConcreteDependentChoiceDefault({
19771
+ inputKey: key,
19772
+ collector,
19773
+ domain,
19774
+ defaultValue,
19775
+ dependencyKeys
19776
+ });
18541
19777
  return [
18542
19778
  {
18543
19779
  key,
18544
19780
  kind: collector.kind,
18545
19781
  domain,
18546
- ..."defaultValue" in collector ? { defaultValue: collector.defaultValue } : dynamicDefault !== void 0 ? { defaultValue: dynamicDefault } : {}
19782
+ ...defaultValue !== void 0 ? { defaultValue } : {}
18547
19783
  }
18548
19784
  ];
18549
19785
  });
18550
19786
  }
19787
+ var concreteDependentChoiceDefaultWarnings = /* @__PURE__ */ new Set();
19788
+ function shouldEmitAuthoringWarning() {
19789
+ const env2 = globalThis.process?.env;
19790
+ return env2?.NODE_ENV !== "production" && env2?.DREAMBOARD_SUPPRESS_AUTHORING_WARNINGS !== "1";
19791
+ }
19792
+ function warnConcreteDependentChoiceDefault({
19793
+ inputKey,
19794
+ collector,
19795
+ domain,
19796
+ defaultValue,
19797
+ dependencyKeys
19798
+ }) {
19799
+ if (!shouldEmitAuthoringWarning() || !dependencyKeys.has(inputKey) || collector.kind !== "form" || domain.type !== "choice" || defaultValue === void 0 || defaultValue === null) {
19800
+ return;
19801
+ }
19802
+ const warningKey = `${inputKey}:${String(defaultValue)}`;
19803
+ if (concreteDependentChoiceDefaultWarnings.has(warningKey)) {
19804
+ return;
19805
+ }
19806
+ concreteDependentChoiceDefaultWarnings.add(warningKey);
19807
+ console.warn(
19808
+ `[dreamboard] Form choice input "${inputKey}" feeds another collector and defaulted to "${String(
19809
+ defaultValue
19810
+ )}". Dependent choices that select the object an action applies to should usually use defaultValue: () => undefined so collection stays explicit.`
19811
+ );
19812
+ }
18551
19813
  function collectFirstCardZoneId(interaction) {
18552
19814
  const collectors = interactionInputsOf(interaction);
18553
19815
  for (const collector of Object.values(collectors)) {
@@ -18661,13 +19923,18 @@ function validateCollectorTargets(interaction, domainState, playerId, params) {
18661
19923
  if (!collector.validateTarget) continue;
18662
19924
  const rawTarget = params[key];
18663
19925
  if (rawTarget === null || rawTarget === void 0) continue;
18664
- const values = valuesForCollectorValidation(collector.selection, rawTarget);
18665
- for (const value of values) {
19926
+ const dependencyValues = dependencyValuesForCollector(collector, params);
19927
+ const targetValues = valuesForCollectorValidation(
19928
+ collector.selection,
19929
+ rawTarget
19930
+ );
19931
+ for (const value of targetValues) {
18666
19932
  const issue = collector.validateTarget(
18667
19933
  domainState,
18668
19934
  playerId,
18669
19935
  queries(),
18670
- value
19936
+ value,
19937
+ dependencyValues
18671
19938
  );
18672
19939
  if (issue) {
18673
19940
  return makeValidationError(issue.errorCode, issue.message);
@@ -18676,6 +19943,13 @@ function validateCollectorTargets(interaction, domainState, playerId, params) {
18676
19943
  }
18677
19944
  return { valid: true };
18678
19945
  }
19946
+ function dependencyValuesForCollector(collector, params) {
19947
+ const dependencies = collector.dependsOn ?? [];
19948
+ if (dependencies.length === 0) return void 0;
19949
+ return Object.fromEntries(
19950
+ dependencies.map((dependencyKey) => [dependencyKey, params[dependencyKey]])
19951
+ );
19952
+ }
18679
19953
  function valuesForCollectorValidation(selection, value) {
18680
19954
  if (selection?.mode === "many") {
18681
19955
  return Array.isArray(value) ? value : [];
@@ -18847,7 +20121,7 @@ function deriveInteractionKind(inputs) {
18847
20121
  }
18848
20122
  return "action";
18849
20123
  }
18850
- function isTargetCollector2(collector) {
20124
+ function isTargetCollector(collector) {
18851
20125
  switch (collector.kind) {
18852
20126
  case "card":
18853
20127
  case "board-edge":
@@ -18862,8 +20136,19 @@ function isTargetCollector2(collector) {
18862
20136
  function isManyCollector(collector) {
18863
20137
  return collector.selection?.mode === "many";
18864
20138
  }
18865
- function isDefaultRenderableFormCollector(collector) {
18866
- return collector.kind === "form" && collector.domain !== void 0;
20139
+ function terminalCollectorsForInputs(inputs) {
20140
+ const terminalKeys = new Set(Object.keys(inputs));
20141
+ for (const collector of Object.values(inputs)) {
20142
+ for (const dependencyKey of collector.dependsOn ?? []) {
20143
+ terminalKeys.delete(dependencyKey);
20144
+ }
20145
+ }
20146
+ const collectors = [];
20147
+ for (const key of terminalKeys) {
20148
+ const collector = inputs[key];
20149
+ if (collector) collectors.push(collector);
20150
+ }
20151
+ return collectors;
18867
20152
  }
18868
20153
  function deriveCommitPolicy(inputs, explicit) {
18869
20154
  const collectors = Object.values(inputs);
@@ -18879,15 +20164,16 @@ function deriveCommitPolicy(inputs, explicit) {
18879
20164
  if (hasManyCollector) {
18880
20165
  return { mode: "manual" };
18881
20166
  }
18882
- if (collectors.some(isDefaultRenderableFormCollector)) {
20167
+ if (collectors.length === 0 || !collectors.some(isTargetCollector)) {
18883
20168
  return { mode: "manual" };
18884
20169
  }
18885
- if (collectors.length === 0 || !collectors.some(isTargetCollector2)) {
20170
+ const terminalCollectors = terminalCollectorsForInputs(inputs);
20171
+ if (terminalCollectors.length === 0) {
18886
20172
  return { mode: "manual" };
18887
20173
  }
18888
- return collectors.every(
18889
- (collector) => isTargetCollector2(collector) || collector.kind === "rng"
18890
- ) ? { mode: "autoWhenReady" } : { mode: "manual" };
20174
+ return terminalCollectors.every(
20175
+ (collector) => isTargetCollector(collector) || collector.kind === "rng"
20176
+ ) && terminalCollectors.some(isTargetCollector) ? { mode: "autoWhenReady" } : { mode: "manual" };
18891
20177
  }
18892
20178
  function projectInteractionMetadata(interaction) {
18893
20179
  return {
@@ -18902,7 +20188,7 @@ function enrichResourceInputPresentation(inputs, manifest) {
18902
20188
  }
18903
20189
  const resources = presentationById;
18904
20190
  const enrichChoice = (choice) => {
18905
- const presentation = resources[choice.value];
20191
+ const presentation = choice.value === null ? void 0 : resources[choice.value];
18906
20192
  return {
18907
20193
  ...choice,
18908
20194
  label: typeof presentation?.label === "string" && (!choice.label || choice.label === choice.value) ? presentation.label : choice.label || (typeof presentation?.label === "string" ? presentation.label : ""),
@@ -18910,7 +20196,16 @@ function enrichResourceInputPresentation(inputs, manifest) {
18910
20196
  };
18911
20197
  };
18912
20198
  return inputs.map((input) => {
18913
- if (input.domain.type === "choice" || input.domain.type === "choiceList") {
20199
+ if (input.domain.type === "choice") {
20200
+ return {
20201
+ ...input,
20202
+ domain: {
20203
+ ...input.domain,
20204
+ choices: input.domain.choices.map(enrichChoice)
20205
+ }
20206
+ };
20207
+ }
20208
+ if (input.domain.type === "choiceList") {
18914
20209
  return {
18915
20210
  ...input,
18916
20211
  domain: {
@@ -18936,6 +20231,23 @@ function enrichResourceInputPresentation(inputs, manifest) {
18936
20231
  };
18937
20232
  });
18938
20233
  }
20234
+ function interactionAvailabilityFromDecision(decision) {
20235
+ if (decision.available) return { status: "available" };
20236
+ if (decision.unavailableReason === "Not your turn") {
20237
+ return { status: "notYourTurn", reason: decision.unavailableReason };
20238
+ }
20239
+ if (decision.unavailableReason === "INSUFFICIENT_RESOURCES") {
20240
+ return {
20241
+ status: "insufficientResources",
20242
+ reason: decision.unavailableReason,
20243
+ missingResources: { ...decision.missingResources ?? {} }
20244
+ };
20245
+ }
20246
+ return {
20247
+ status: "blocked",
20248
+ reason: decision.unavailableReason ?? "Interaction unavailable"
20249
+ };
20250
+ }
18939
20251
  function createInteractionDecisionResolver(scope, stages, authorization) {
18940
20252
  function evaluateInteractionCost(state, interaction, playerId, params, projection) {
18941
20253
  if (!interaction.cost) {
@@ -18969,15 +20281,15 @@ function createInteractionDecisionResolver(scope, stages, authorization) {
18969
20281
  const queries = options.projection?.q ?? createStateQueries(domainState);
18970
20282
  const derived = options.projection?.derived;
18971
20283
  const shouldMaterializeInputDomains = decision.available || decision.unavailableReason !== "Not your turn";
18972
- const promptContext = metadata.kind === "prompt" && shouldMaterializeInputDomains ? {
20284
+ const promptContext = metadata.kind === "prompt" ? {
18973
20285
  to: playerId,
18974
20286
  title: humanizeInteractionId(interactionId),
18975
- options: collectPromptOptions(
20287
+ options: shouldMaterializeInputDomains ? collectPromptOptions(
18976
20288
  { inputs: interactionInputs },
18977
20289
  domainState,
18978
20290
  playerId,
18979
20291
  queries
18980
- )
20292
+ ) : void 0
18981
20293
  } : void 0;
18982
20294
  const inputs = shouldMaterializeInputDomains ? enrichResourceInputPresentation(
18983
20295
  collectInteractionInputs(interaction, domainState, playerId, {
@@ -18991,11 +20303,11 @@ function createInteractionDecisionResolver(scope, stages, authorization) {
18991
20303
  }),
18992
20304
  scope.definition.contract.manifest
18993
20305
  ) : [];
18994
- return {
20306
+ const baseDescriptor = {
18995
20307
  phaseName,
18996
20308
  interactionKey: `${phaseName}.${interactionId}`,
18997
20309
  interactionId,
18998
- ...metadata,
20310
+ commit: metadata.commit,
18999
20311
  zoneId: collectFirstCardZoneId(interaction),
19000
20312
  zoneIds: collectCardZoneIds(interaction),
19001
20313
  inputs,
@@ -19003,10 +20315,21 @@ function createInteractionDecisionResolver(scope, stages, authorization) {
19003
20315
  currentResources: decision.cost ? {
19004
20316
  ...queries.player.resources(playerId)
19005
20317
  } : void 0,
19006
- missingResources: decision.missingResources,
19007
- available: decision.available,
19008
- unavailableReason: decision.unavailableReason,
19009
- context: promptContext
20318
+ availability: interactionAvailabilityFromDecision(decision)
20319
+ };
20320
+ if (metadata.kind === "prompt") {
20321
+ return {
20322
+ ...baseDescriptor,
20323
+ kind: "prompt",
20324
+ context: promptContext ?? {
20325
+ to: playerId,
20326
+ title: humanizeInteractionId(interactionId)
20327
+ }
20328
+ };
20329
+ }
20330
+ return {
20331
+ ...baseDescriptor,
20332
+ kind: "action"
19010
20333
  };
19011
20334
  }
19012
20335
  function resolveInteractionDecision({
@@ -19422,16 +20745,15 @@ function resolveContainerRef(table, ref) {
19422
20745
  throw new Error(`Unknown board '${ref.boardId}'.`);
19423
20746
  }
19424
20747
  const space2 = board2?.spaces[ref.spaceId];
19425
- if (!space2?.zoneId) {
20748
+ if (!space2) {
19426
20749
  throw new Error(
19427
- `Board space '${ref.spaceId}' on board '${ref.boardId}' has no container zone.`
20750
+ `Unknown board space '${ref.spaceId}' on board '${ref.boardId}'.`
19428
20751
  );
19429
20752
  }
19430
20753
  return {
19431
20754
  kind: "space",
19432
20755
  boardId: board2.id,
19433
- spaceId: ref.spaceId,
19434
- zoneId: space2.zoneId
20756
+ spaceId: ref.spaceId
19435
20757
  };
19436
20758
  }
19437
20759
  const board = table.boards.byId[resolveRuntimeBoardId(table, ref.boardId, ref.playerId)];
@@ -19441,16 +20763,15 @@ function resolveContainerRef(table, ref) {
19441
20763
  );
19442
20764
  }
19443
20765
  const space = board?.spaces[ref.spaceId];
19444
- if (!space?.zoneId) {
20766
+ if (!space) {
19445
20767
  throw new Error(
19446
- `Board space '${ref.spaceId}' on board '${ref.boardId}' for '${ref.playerId}' has no container zone.`
20768
+ `Unknown board space '${ref.spaceId}' on board '${ref.boardId}' for '${ref.playerId}'.`
19447
20769
  );
19448
20770
  }
19449
20771
  return {
19450
20772
  kind: "space",
19451
20773
  boardId: board.id,
19452
- spaceId: ref.spaceId,
19453
- zoneId: space.zoneId
20774
+ spaceId: ref.spaceId
19454
20775
  };
19455
20776
  }
19456
20777
  function readContainerComponents(table, ref) {
@@ -20072,7 +21393,7 @@ function createProjectionBuilder(scope, interactions) {
20072
21393
  });
20073
21394
  if (!decision.found || !decision.visible) continue;
20074
21395
  const cardDomain = cardKey ? decision.descriptor.inputs.find((input) => input.key === cardKey)?.domain : void 0;
20075
- const cardTargets = cardDomain?.type === "target" ? cardDomain.eligibleTargets : void 0;
21396
+ const cardTargets = cardDomain?.type === "cardTarget" && cardDomain.projection === "resolved" ? cardDomain.eligibleTargets : void 0;
20076
21397
  if (cardKey && !cardTargets?.includes(cardId)) {
20077
21398
  continue;
20078
21399
  }
@@ -20881,6 +22202,9 @@ function toWireDispatchResult(result, serializeState) {
20881
22202
  trace
20882
22203
  };
20883
22204
  }
22205
+ function toWireDispatchTrace(result) {
22206
+ return toWireDispatchResult(result, (state) => state).trace;
22207
+ }
20884
22208
  function createReducerBundle(definition) {
20885
22209
  const trustedBundle = createTrustedReducerBundle(definition);
20886
22210
  const codec = createIngressRuntimeCodec(definition);
@@ -21043,7 +22367,11 @@ function createReducerBundle(definition) {
21043
22367
  };
21044
22368
  }
21045
22369
  state = result.state;
21046
- return { kind: "accept" };
22370
+ return {
22371
+ kind: "accept",
22372
+ state: codec.serializeState(state),
22373
+ trace: toWireDispatchTrace(result)
22374
+ };
21047
22375
  },
21048
22376
  projectSeatViewDynamic({ playerId, viewId = "player" }) {
21049
22377
  return trustedBundle.projectSeatViewDynamic({
@@ -21077,7 +22405,7 @@ function createReducerBundle(definition) {
21077
22405
  // src/utils/ts-module-loader.ts
21078
22406
  import { mkdtemp as mkdtemp2, rm as rm4, writeFile as writeFile3 } from "fs/promises";
21079
22407
  import { tmpdir as tmpdir2 } from "os";
21080
- import path18 from "path";
22408
+ import path19 from "path";
21081
22409
  import { pathToFileURL as pathToFileURL2 } from "url";
21082
22410
  import { build as build2 } from "esbuild";
21083
22411
  var ESBUILD_EXTERNALS = [
@@ -21091,14 +22419,14 @@ function resolveSourceCheckoutBuildContext() {
21091
22419
  const repoRoot = resolveCliRepoRoot(import.meta.url);
21092
22420
  return {
21093
22421
  nodePaths: [
21094
- path18.join(repoRoot, "apps", "dreamboard-cli", "node_modules"),
21095
- path18.join(repoRoot, "node_modules")
22422
+ path19.join(repoRoot, "apps", "dreamboard-cli", "node_modules"),
22423
+ path19.join(repoRoot, "node_modules")
21096
22424
  ],
21097
22425
  plugins: [createRepoLocalPackageResolutionPlugin({ repoRoot })]
21098
22426
  };
21099
22427
  } catch {
21100
22428
  return {
21101
- nodePaths: [path18.join(process.cwd(), "node_modules")],
22429
+ nodePaths: [path19.join(process.cwd(), "node_modules")],
21102
22430
  plugins: []
21103
22431
  };
21104
22432
  }
@@ -21124,10 +22452,10 @@ async function bundleTypeScriptModuleText(entryPath, options = {}) {
21124
22452
  return output.text;
21125
22453
  }
21126
22454
  async function importTypeScriptModule(entryPath) {
21127
- const tempDir = await mkdtemp2(path18.join(tmpdir2(), "dreamboard-ts-module-"));
21128
- const outfile = path18.join(
22455
+ const tempDir = await mkdtemp2(path19.join(tmpdir2(), "dreamboard-ts-module-"));
22456
+ const outfile = path19.join(
21129
22457
  tempDir,
21130
- `${path18.basename(entryPath).replace(/\.[^.]+$/u, "")}.mjs`
22458
+ `${path19.basename(entryPath).replace(/\.[^.]+$/u, "")}.mjs`
21131
22459
  );
21132
22460
  try {
21133
22461
  const bundledText = await bundleTypeScriptModuleText(entryPath);
@@ -21152,7 +22480,7 @@ import {
21152
22480
  symlink
21153
22481
  } from "fs/promises";
21154
22482
  import { tmpdir as tmpdir3 } from "os";
21155
- import path19 from "path";
22483
+ import path20 from "path";
21156
22484
  import { createInterface } from "readline";
21157
22485
  import { fileURLToPath as fileURLToPath7 } from "url";
21158
22486
  var READY_PREFIX = "HARNESS_READY ";
@@ -21160,15 +22488,15 @@ var HARNESS_START_IDLE_TIMEOUT_MS = 18e4;
21160
22488
  var HARNESS_START_MAX_TIMEOUT_MS = 6e5;
21161
22489
  var HARNESS_STOP_TIMEOUT_MS = 1e4;
21162
22490
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
21163
- var MODULE_DIR4 = path19.dirname(fileURLToPath7(import.meta.url));
21164
- var REPO_ROOT3 = path19.resolve(MODULE_DIR4, "../../../../..");
21165
- var PROJECT_DEPENDENCY_NODE_MODULES = path19.join(
22491
+ var MODULE_DIR4 = path20.dirname(fileURLToPath7(import.meta.url));
22492
+ var REPO_ROOT3 = path20.resolve(MODULE_DIR4, "../../../../..");
22493
+ var PROJECT_DEPENDENCY_NODE_MODULES = path20.join(
21166
22494
  REPO_ROOT3,
21167
22495
  "apps",
21168
22496
  "dreamboard-cli",
21169
22497
  "node_modules"
21170
22498
  );
21171
- var UI_SDK_NODE_MODULES = path19.join(
22499
+ var UI_SDK_NODE_MODULES = path20.join(
21172
22500
  REPO_ROOT3,
21173
22501
  "packages",
21174
22502
  "ui-sdk",
@@ -21178,8 +22506,8 @@ async function startEmbeddedHarnessSession(options) {
21178
22506
  const repoRoot = await resolveRepoRoot2(options.projectRoot);
21179
22507
  const gradlew = await resolveGradleLauncher(repoRoot);
21180
22508
  const fixture = await prepareLocalHarnessFixture(options);
21181
- const logDir = path19.join(options.projectRoot, "test", "generated");
21182
- const logFilePath = path19.join(logDir, "embedded-harness.log");
22509
+ const logDir = path20.join(options.projectRoot, "test", "generated");
22510
+ const logFilePath = path20.join(logDir, "embedded-harness.log");
21183
22511
  await mkdir2(logDir, { recursive: true });
21184
22512
  const previousClientConfig = client.getConfig();
21185
22513
  const child = spawn5(
@@ -21188,7 +22516,7 @@ async function startEmbeddedHarnessSession(options) {
21188
22516
  ...gradlew.args,
21189
22517
  "--console=plain",
21190
22518
  ":apps:backend:runEmbeddedHarness",
21191
- `-PharnessManifestPath=${path19.join(options.projectRoot, MATERIALIZED_MANIFEST_FILE)}`,
22519
+ `-PharnessManifestPath=${path20.join(options.projectRoot, MATERIALIZED_MANIFEST_FILE)}`,
21192
22520
  `-PharnessBundlePath=${fixture.bundleRoot}`,
21193
22521
  `-PharnessGameId=${fixture.gameId}`,
21194
22522
  `-PharnessManifestId=${fixture.manifestId}`,
@@ -21255,7 +22583,7 @@ async function ensureSymlink(targetPath, linkPath) {
21255
22583
  if (await pathExists3(linkPath)) {
21256
22584
  return;
21257
22585
  }
21258
- await mkdir2(path19.dirname(linkPath), { recursive: true });
22586
+ await mkdir2(path20.dirname(linkPath), { recursive: true });
21259
22587
  await symlink(
21260
22588
  targetPath,
21261
22589
  linkPath,
@@ -21266,13 +22594,13 @@ async function ensureEmbeddedHarnessDependencies(projectRoot) {
21266
22594
  if (await pathExists3(PROJECT_DEPENDENCY_NODE_MODULES)) {
21267
22595
  await ensureSymlink(
21268
22596
  PROJECT_DEPENDENCY_NODE_MODULES,
21269
- path19.join(projectRoot, "node_modules")
22597
+ path20.join(projectRoot, "node_modules")
21270
22598
  );
21271
22599
  }
21272
22600
  if (await pathExists3(UI_SDK_NODE_MODULES)) {
21273
22601
  await ensureSymlink(
21274
22602
  UI_SDK_NODE_MODULES,
21275
- path19.join(projectRoot, "ui", "node_modules")
22603
+ path20.join(projectRoot, "ui", "node_modules")
21276
22604
  );
21277
22605
  }
21278
22606
  }
@@ -21282,15 +22610,15 @@ async function prepareLocalHarnessFixture(options) {
21282
22610
  const manifestContent = await readMaterializedManifestText(
21283
22611
  options.projectRoot
21284
22612
  );
21285
- const appEntryPath = path19.join(options.projectRoot, "app", "index.ts");
22613
+ const appEntryPath = path20.join(options.projectRoot, "app", "index.ts");
21286
22614
  if (!await exists(appEntryPath)) {
21287
22615
  throw new Error(
21288
- `Embedded harness requires ${path19.relative(options.projectRoot, appEntryPath)}.`
22616
+ `Embedded harness requires ${path20.relative(options.projectRoot, appEntryPath)}.`
21289
22617
  );
21290
22618
  }
21291
- const bundleRoot = await mkdtemp3(path19.join(tmpdir3(), "dreamboard-harness-"));
21292
- const bundleEntryPath = path19.join(bundleRoot, "src", "app", "index.js");
21293
- await mkdir2(path19.dirname(bundleEntryPath), { recursive: true });
22619
+ const bundleRoot = await mkdtemp3(path20.join(tmpdir3(), "dreamboard-harness-"));
22620
+ const bundleEntryPath = path20.join(bundleRoot, "src", "app", "index.js");
22621
+ await mkdir2(path20.dirname(bundleEntryPath), { recursive: true });
21294
22622
  const bunExecutable = await resolveBunExecutable();
21295
22623
  if (!bunExecutable) {
21296
22624
  await rm5(bundleRoot, { recursive: true, force: true });
@@ -21327,7 +22655,7 @@ async function prepareLocalHarnessFixture(options) {
21327
22655
  const bundleContent = await readFile3(bundleEntryPath, "utf8");
21328
22656
  const manifestHash = hashContent(manifestContent);
21329
22657
  const bundleHash = hashContent(bundleContent);
21330
- const gameSlug = options.projectConfig.slug || path19.basename(options.projectRoot);
22658
+ const gameSlug = options.projectConfig.slug || path20.basename(options.projectRoot);
21331
22659
  const authoring = getProjectAuthoringState(options.projectConfig);
21332
22660
  return {
21333
22661
  bundleRoot,
@@ -21347,14 +22675,14 @@ async function prepareLocalHarnessFixture(options) {
21347
22675
  };
21348
22676
  }
21349
22677
  async function resolveBunExecutable() {
21350
- const currentExecutable = path19.basename(process.execPath).toLowerCase();
22678
+ const currentExecutable = path20.basename(process.execPath).toLowerCase();
21351
22679
  if (currentExecutable === "bun" || currentExecutable === "bun.exe") {
21352
22680
  return process.execPath;
21353
22681
  }
21354
22682
  const candidatePaths = [
21355
- ...(process.env.PATH ?? "").split(path19.delimiter).filter((directory) => directory.length > 0).flatMap((directory) => executableCandidates(directory, "bun")),
21356
- ...process.env.BUN_INSTALL ? executableCandidates(path19.join(process.env.BUN_INSTALL, "bin"), "bun") : [],
21357
- ...process.env.HOME ? executableCandidates(path19.join(process.env.HOME, ".bun", "bin"), "bun") : []
22683
+ ...(process.env.PATH ?? "").split(path20.delimiter).filter((directory) => directory.length > 0).flatMap((directory) => executableCandidates(directory, "bun")),
22684
+ ...process.env.BUN_INSTALL ? executableCandidates(path20.join(process.env.BUN_INSTALL, "bin"), "bun") : [],
22685
+ ...process.env.HOME ? executableCandidates(path20.join(process.env.HOME, ".bun", "bin"), "bun") : []
21358
22686
  ];
21359
22687
  for (const candidate of candidatePaths) {
21360
22688
  try {
@@ -21370,11 +22698,11 @@ function executableCandidates(directory, executableName) {
21370
22698
  return [];
21371
22699
  }
21372
22700
  if (process.platform !== "win32") {
21373
- return [path19.join(directory, executableName)];
22701
+ return [path20.join(directory, executableName)];
21374
22702
  }
21375
22703
  const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter((extension) => extension.length > 0);
21376
22704
  return extensions.map(
21377
- (extension) => path19.join(directory, `${executableName}${extension.toLowerCase()}`)
22705
+ (extension) => path20.join(directory, `${executableName}${extension.toLowerCase()}`)
21378
22706
  );
21379
22707
  }
21380
22708
  async function waitForExitCode(child) {
@@ -21532,13 +22860,13 @@ async function waitForExit(child, timeoutMs) {
21532
22860
  }
21533
22861
  async function resolveGradleLauncher(repoRoot) {
21534
22862
  if (process.platform === "win32") {
21535
- const gradleBat = path19.join(repoRoot, "gradlew.bat");
22863
+ const gradleBat = path20.join(repoRoot, "gradlew.bat");
21536
22864
  if (!await exists(gradleBat)) {
21537
22865
  throw new Error(`Missing Gradle launcher at ${gradleBat}`);
21538
22866
  }
21539
22867
  return { command: gradleBat, args: [] };
21540
22868
  }
21541
- const gradleShell = path19.join(repoRoot, "gradlew");
22869
+ const gradleShell = path20.join(repoRoot, "gradlew");
21542
22870
  if (!await exists(gradleShell)) {
21543
22871
  throw new Error(`Missing Gradle launcher at ${gradleShell}`);
21544
22872
  }
@@ -21546,7 +22874,7 @@ async function resolveGradleLauncher(repoRoot) {
21546
22874
  }
21547
22875
  async function resolveRepoRoot2(projectRoot) {
21548
22876
  const currentFile = fileURLToPath7(import.meta.url);
21549
- const searchRoots = [path19.dirname(currentFile), projectRoot, process.cwd()];
22877
+ const searchRoots = [path20.dirname(currentFile), projectRoot, process.cwd()];
21550
22878
  for (const root of searchRoots) {
21551
22879
  const repoRoot = await findSourceCheckoutRepoRoot(root);
21552
22880
  if (repoRoot) {
@@ -21558,12 +22886,12 @@ async function resolveRepoRoot2(projectRoot) {
21558
22886
  );
21559
22887
  }
21560
22888
  async function findSourceCheckoutRepoRoot(startDir) {
21561
- let currentDir = path19.resolve(startDir);
22889
+ let currentDir = path20.resolve(startDir);
21562
22890
  while (true) {
21563
22891
  if (await isSourceCheckoutRepoRoot(currentDir)) {
21564
22892
  return currentDir;
21565
22893
  }
21566
- const parentDir = path19.dirname(currentDir);
22894
+ const parentDir = path20.dirname(currentDir);
21567
22895
  if (parentDir === currentDir) {
21568
22896
  return null;
21569
22897
  }
@@ -21571,7 +22899,7 @@ async function findSourceCheckoutRepoRoot(startDir) {
21571
22899
  }
21572
22900
  }
21573
22901
  async function isSourceCheckoutRepoRoot(rootDir) {
21574
- return await exists(path19.join(rootDir, "gradlew")) && await exists(path19.join(rootDir, "apps", "backend", "build.gradle.kts"));
22902
+ return await exists(path20.join(rootDir, "gradlew")) && await exists(path20.join(rootDir, "apps", "backend", "build.gradle.kts"));
21575
22903
  }
21576
22904
  function normalizeUuid(candidate, fallbackSeed) {
21577
22905
  if (candidate && UUID_PATTERN.test(candidate)) {
@@ -21623,7 +22951,7 @@ async function subscribeToCliSessionEvents(options) {
21623
22951
 
21624
22952
  // src/ui/playwright-runner.ts
21625
22953
  import os2 from "os";
21626
- import path20 from "path";
22954
+ import path21 from "path";
21627
22955
  function configurePlaywrightBrowsersPath() {
21628
22956
  if (process.env.PLAYWRIGHT_BROWSERS_PATH) {
21629
22957
  return;
@@ -21635,10 +22963,10 @@ function configurePlaywrightBrowsersPath() {
21635
22963
  } catch {
21636
22964
  return;
21637
22965
  }
21638
- if (!runtimeHome || path20.resolve(runtimeHome) === path20.resolve(realHome)) {
22966
+ if (!runtimeHome || path21.resolve(runtimeHome) === path21.resolve(realHome)) {
21639
22967
  return;
21640
22968
  }
21641
- const browserCachePath = process.platform === "darwin" ? path20.join(realHome, "Library", "Caches", "ms-playwright") : process.platform === "win32" ? path20.join(realHome, "AppData", "Local", "ms-playwright") : path20.join(realHome, ".cache", "ms-playwright");
22969
+ const browserCachePath = process.platform === "darwin" ? path21.join(realHome, "Library", "Caches", "ms-playwright") : process.platform === "win32" ? path21.join(realHome, "AppData", "Local", "ms-playwright") : path21.join(realHome, ".cache", "ms-playwright");
21642
22970
  process.env.PLAYWRIGHT_BROWSERS_PATH = browserCachePath;
21643
22971
  }
21644
22972
  async function buildBrowserAuthInitScript(config) {
@@ -21647,7 +22975,7 @@ async function buildBrowserAuthInitScript(config) {
21647
22975
  return `(function(){localStorage.setItem('supabase_token',${JSON.stringify(config.authToken)});})();`;
21648
22976
  }
21649
22977
  try {
21650
- const { createClient: createClient2 } = await import("./dist-FRURQI7Q.js");
22978
+ const { createClient: createClient2 } = await import("./dist-JNA2CLKS.js");
21651
22979
  const supabase = createClient2(config.supabaseUrl, config.supabaseAnonKey);
21652
22980
  let sessionData = null;
21653
22981
  if (!sessionData) {
@@ -21741,9 +23069,42 @@ var TESTING_TYPES_STUB2 = "export function defineScenario(scenario) { return sce
21741
23069
  var DEFAULT_TIMEOUT_MS = 1e4;
21742
23070
  var BASE_SUFFIX = ".base.ts";
21743
23071
  var SCENARIO_SUFFIX = ".scenario.ts";
23072
+ function formatScenarioErrorForDisplay(options) {
23073
+ const message = options.error.message || "Scenario failed.";
23074
+ const scenarioFrame = findScenarioStackFrame({
23075
+ stack: options.error.stack,
23076
+ projectRoot: options.projectRoot,
23077
+ scenarioFilePath: options.scenarioFilePath
23078
+ });
23079
+ return scenarioFrame ? `${message}
23080
+ ${scenarioFrame}` : message;
23081
+ }
23082
+ function findScenarioStackFrame(options) {
23083
+ if (!options.stack) {
23084
+ return null;
23085
+ }
23086
+ const absolutePath = path22.resolve(options.scenarioFilePath);
23087
+ const relativePath = path22.relative(options.projectRoot, absolutePath);
23088
+ const normalizedRelativePath = relativePath.split(path22.sep).join("/");
23089
+ const escapedAbsolutePath = escapeRegExp(absolutePath);
23090
+ const escapedRelativePath = escapeRegExp(normalizedRelativePath);
23091
+ const absoluteFrame = new RegExp(`${escapedAbsolutePath}:(\\d+):(\\d+)`);
23092
+ const relativeFrame = new RegExp(`${escapedRelativePath}:(\\d+):(\\d+)`);
23093
+ for (const line of options.stack.split("\n")) {
23094
+ const normalizedLine = line.split(path22.sep).join("/");
23095
+ const match = normalizedLine.match(absoluteFrame) ?? normalizedLine.match(relativeFrame);
23096
+ if (match?.[1] && match?.[2]) {
23097
+ return `at ${normalizedRelativePath}:${match[1]}:${match[2]}`;
23098
+ }
23099
+ }
23100
+ return null;
23101
+ }
23102
+ function escapeRegExp(value) {
23103
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23104
+ }
21744
23105
  var testingExpectApiFactoryPromise;
21745
23106
  async function loadTestingExpectApiFactory() {
21746
- testingExpectApiFactoryPromise ??= import("./dist-FEPN3BDN.js").then(
23107
+ testingExpectApiFactoryPromise ??= import("./dist-JO2OGKJD.js").then(
21747
23108
  (module) => module.createExpectApi
21748
23109
  );
21749
23110
  return testingExpectApiFactoryPromise;
@@ -21783,7 +23144,7 @@ async function discoverFiles(root, suffix) {
21783
23144
  const entries = await readdir3(root, { withFileTypes: true });
21784
23145
  const files = [];
21785
23146
  for (const entry of entries) {
21786
- const entryPath = path21.join(root, entry.name);
23147
+ const entryPath = path22.join(root, entry.name);
21787
23148
  if (entry.isDirectory()) {
21788
23149
  files.push(...await discoverFiles(entryPath, suffix));
21789
23150
  continue;
@@ -21832,38 +23193,38 @@ function parseTypedScenarioDefinition(value) {
21832
23193
  };
21833
23194
  }
21834
23195
  async function isReducerNativeTestingWorkspace(projectRoot) {
21835
- return await exists(path21.join(projectRoot, "app", "game.ts")) && await exists(
21836
- path21.join(projectRoot, "shared", "generated", "ui-contract.ts")
23196
+ return await exists(path22.join(projectRoot, "app", "game.ts")) && await exists(
23197
+ path22.join(projectRoot, "shared", "generated", "ui-contract.ts")
21837
23198
  );
21838
23199
  }
21839
23200
  async function ensureReducerNativeTestingFiles(projectRoot) {
21840
- const testingTypesPath = path21.join(projectRoot, "test", "testing-types.ts");
21841
- const testingContractPath = path21.join(
23201
+ const testingTypesPath = path22.join(projectRoot, "test", "testing-types.ts");
23202
+ const testingContractPath = path22.join(
21842
23203
  projectRoot,
21843
23204
  "test",
21844
23205
  "generated",
21845
23206
  "testing-contract.ts"
21846
23207
  );
21847
- const baseStatesPath = path21.join(
23208
+ const baseStatesPath = path22.join(
21848
23209
  projectRoot,
21849
23210
  "test",
21850
23211
  "generated",
21851
23212
  "base-states.generated.ts"
21852
23213
  );
21853
- const baseStatesDtsPath = path21.join(
23214
+ const baseStatesDtsPath = path22.join(
21854
23215
  projectRoot,
21855
23216
  "test",
21856
23217
  "generated",
21857
23218
  "base-states.generated.d.ts"
21858
23219
  );
21859
- const scenarioManifestPath = path21.join(
23220
+ const scenarioManifestPath = path22.join(
21860
23221
  projectRoot,
21861
23222
  "test",
21862
23223
  "generated",
21863
23224
  "scenario-manifest.generated.ts"
21864
23225
  );
21865
- await ensureDir(path21.dirname(testingTypesPath));
21866
- await ensureDir(path21.dirname(testingContractPath));
23226
+ await ensureDir(path22.dirname(testingTypesPath));
23227
+ await ensureDir(path22.dirname(testingContractPath));
21867
23228
  const existingTestingTypes = await readTextFileIfExists(testingTypesPath);
21868
23229
  if (shouldRefreshReducerTestingTypes(existingTestingTypes)) {
21869
23230
  await writeTextFile(
@@ -21907,7 +23268,7 @@ var DEFAULT_REJECTION_CODES2 = [
21907
23268
  ];
21908
23269
  async function collectKnownRejectionCodes(projectRoot) {
21909
23270
  const knownCodes = new Set(DEFAULT_REJECTION_CODES2);
21910
- const gamePath = path21.join(projectRoot, "app", "game.ts");
23271
+ const gamePath = path22.join(projectRoot, "app", "game.ts");
21911
23272
  if (!await exists(gamePath)) {
21912
23273
  return Array.from(knownCodes).sort(
21913
23274
  (left, right) => left.localeCompare(right)
@@ -21934,7 +23295,7 @@ async function collectKnownRejectionCodes(projectRoot) {
21934
23295
  }
21935
23296
  async function loadTypedBases(projectRoot) {
21936
23297
  const baseFiles = await discoverFiles(
21937
- path21.join(projectRoot, "test", "bases"),
23298
+ path22.join(projectRoot, "test", "bases"),
21938
23299
  BASE_SUFFIX
21939
23300
  );
21940
23301
  const loaded = [];
@@ -21953,8 +23314,8 @@ async function loadTypedBases(projectRoot) {
21953
23314
  return loaded;
21954
23315
  }
21955
23316
  async function loadTypedScenarios(projectRoot, options) {
21956
- const scenarioFiles = options.scenarioPath ? [path21.resolve(projectRoot, options.scenarioPath)] : await discoverFiles(
21957
- path21.join(projectRoot, "test", "scenarios"),
23317
+ const scenarioFiles = options.scenarioPath ? [path22.resolve(projectRoot, options.scenarioPath)] : await discoverFiles(
23318
+ path22.join(projectRoot, "test", "scenarios"),
21958
23319
  SCENARIO_SUFFIX
21959
23320
  );
21960
23321
  const loaded = [];
@@ -21973,8 +23334,8 @@ async function loadTypedScenarios(projectRoot, options) {
21973
23334
  return loaded;
21974
23335
  }
21975
23336
  function reducerNativeTestHelperExternals(projectRoot, filePath) {
21976
- const testingTypesPath = path21.join(projectRoot, "test", "testing-types");
21977
- const relative = path21.relative(path21.dirname(filePath), testingTypesPath).replaceAll("\\", "/");
23337
+ const testingTypesPath = path22.join(projectRoot, "test", "testing-types");
23338
+ const relative = path22.relative(path22.dirname(filePath), testingTypesPath).replaceAll("\\", "/");
21978
23339
  const specifier = relative.startsWith(".") ? relative : `./${relative}`;
21979
23340
  return [specifier, `${specifier}.ts`];
21980
23341
  }
@@ -22479,7 +23840,7 @@ async function assertLiveMatchesShadow(runner, shadow, live) {
22479
23840
  }
22480
23841
  }
22481
23842
  async function loadBrowserDriver(projectRoot) {
22482
- const filePath = path21.join(projectRoot, "test", "browser-driver.ts");
23843
+ const filePath = path22.join(projectRoot, "test", "browser-driver.ts");
22483
23844
  if (!await exists(filePath)) {
22484
23845
  return null;
22485
23846
  }
@@ -22507,7 +23868,7 @@ function sanitizeSnapshotSegment(value) {
22507
23868
  function createScenarioSnapshotMatcher(options) {
22508
23869
  return (filename, actual) => {
22509
23870
  const suffix = filename ? `.${sanitizeSnapshotSegment(filename)}` : "";
22510
- const snapshotPath = path21.join(
23871
+ const snapshotPath = path22.join(
22511
23872
  options.projectRoot,
22512
23873
  "test",
22513
23874
  "generated",
@@ -22519,15 +23880,15 @@ function createScenarioSnapshotMatcher(options) {
22519
23880
  };
22520
23881
  const serialized = `${JSON.stringify(wrappedValue, null, 2)}
22521
23882
  `;
22522
- mkdirSync2(path21.dirname(snapshotPath), { recursive: true });
23883
+ mkdirSync2(path22.dirname(snapshotPath), { recursive: true });
22523
23884
  if (!existsSync8(snapshotPath) || options.updateSnapshots) {
22524
23885
  writeFileSync2(snapshotPath, serialized, "utf8");
22525
23886
  return;
22526
23887
  }
22527
- const previous = JSON.parse(readFileSync3(snapshotPath, "utf8"));
23888
+ const previous = JSON.parse(readFileSync4(snapshotPath, "utf8"));
22528
23889
  if (!deepEqual(previous.value, actual)) {
22529
23890
  throw new Error(
22530
- `Snapshot mismatch for scenario '${options.scenarioId}'. Re-run with --update-snapshots to refresh ${path21.relative(options.projectRoot, snapshotPath)}.`
23891
+ `Snapshot mismatch for scenario '${options.scenarioId}'. Re-run with --update-snapshots to refresh ${path22.relative(options.projectRoot, snapshotPath)}.`
22531
23892
  );
22532
23893
  }
22533
23894
  };
@@ -22788,7 +24149,7 @@ async function createSessionFromScenario(options) {
22788
24149
  }
22789
24150
  var MATERIALIZED_SCENARIO_CACHE_VERSION = 1;
22790
24151
  function materializedScenarioCachePath(options) {
22791
- return path21.join(
24152
+ return path22.join(
22792
24153
  options.projectRoot,
22793
24154
  PROJECT_DIR_NAME,
22794
24155
  "dev",
@@ -22816,7 +24177,7 @@ async function readMaterializedScenarioCache(options) {
22816
24177
  }
22817
24178
  async function writeMaterializedScenarioCache(options) {
22818
24179
  const cachePath = materializedScenarioCachePath(options);
22819
- await ensureDir(path21.dirname(cachePath));
24180
+ await ensureDir(path22.dirname(cachePath));
22820
24181
  await writeJsonFile(cachePath, {
22821
24182
  cacheVersion: MATERIALIZED_SCENARIO_CACHE_VERSION,
22822
24183
  fingerprintHash: options.fingerprintHash,
@@ -22913,10 +24274,10 @@ async function materializeScenarioReducerState(options) {
22913
24274
  }
22914
24275
  const [gameModule, manifestContractModule] = await Promise.all([
22915
24276
  importTypeScriptModule(
22916
- path21.join(options.projectRoot, "app", "game.ts")
24277
+ path22.join(options.projectRoot, "app", "game.ts")
22917
24278
  ),
22918
24279
  importTypeScriptModule(
22919
- path21.join(options.projectRoot, "shared", "manifest-contract.ts")
24280
+ path22.join(options.projectRoot, "shared", "manifest-contract.ts")
22920
24281
  )
22921
24282
  ]);
22922
24283
  const createInitialTable = typeof manifestContractModule.createInitialTable === "function" ? manifestContractModule.createInitialTable : null;
@@ -22998,10 +24359,10 @@ async function createScenarioActionPlan(options) {
22998
24359
  loadGeneratedBaseStates(options.projectRoot),
22999
24360
  loadManifest(options.projectRoot),
23000
24361
  importTypeScriptModule(
23001
- path21.join(options.projectRoot, "app", "game.ts")
24362
+ path22.join(options.projectRoot, "app", "game.ts")
23002
24363
  ),
23003
24364
  importTypeScriptModule(
23004
- path21.join(options.projectRoot, "shared", "manifest-contract.ts")
24365
+ path22.join(options.projectRoot, "shared", "manifest-contract.ts")
23005
24366
  )
23006
24367
  ]);
23007
24368
  const matchingScenarios = scenarios.filter(
@@ -23268,7 +24629,7 @@ function sortBasesForArtifacts(bases, basesById) {
23268
24629
  return ordered;
23269
24630
  }
23270
24631
  function writeProjectionSnapshots(options) {
23271
- const projectionDir = path21.join(
24632
+ const projectionDir = path22.join(
23272
24633
  options.projectRoot,
23273
24634
  "test",
23274
24635
  "generated",
@@ -23279,7 +24640,7 @@ function writeProjectionSnapshots(options) {
23279
24640
  mkdirSync2(projectionDir, { recursive: true });
23280
24641
  const projection = options.shadow.projectAllSeats();
23281
24642
  for (const [playerId, seatProjection] of Object.entries(projection.seats)) {
23282
- const outputPath = path21.join(projectionDir, `${playerId}.projection.json`);
24643
+ const outputPath = path22.join(projectionDir, `${playerId}.projection.json`);
23283
24644
  writeFileSync2(
23284
24645
  outputPath,
23285
24646
  `${JSON.stringify(
@@ -23299,7 +24660,7 @@ function writeProjectionSnapshots(options) {
23299
24660
  function validateTypedScenarioBases(scenarios, basesById) {
23300
24661
  const available = new Set(basesById.keys());
23301
24662
  const invalid = scenarios.filter((scenario) => !available.has(scenario.definition.from)).map(
23302
- (scenario) => path21.relative(process.cwd(), scenario.filePath) + ` -> '${scenario.definition.from}'`
24663
+ (scenario) => path22.relative(process.cwd(), scenario.filePath) + ` -> '${scenario.definition.from}'`
23303
24664
  );
23304
24665
  if (invalid.length > 0) {
23305
24666
  throw new Error(
@@ -23313,23 +24674,23 @@ async function writeReducerNativeGeneratedFiles(options) {
23313
24674
  const manifestHash = hashContent(JSON.stringify(manifest));
23314
24675
  const appBundleHash = hashContent(
23315
24676
  await bundleTypeScriptModuleText(
23316
- path21.join(options.projectRoot, "app", "game.ts")
24677
+ path22.join(options.projectRoot, "app", "game.ts")
23317
24678
  )
23318
24679
  );
23319
24680
  const uiContractHash = hashContent(
23320
24681
  await bundleTypeScriptModuleText(
23321
- path21.join(options.projectRoot, "shared", "generated", "ui-contract.ts")
24682
+ path22.join(options.projectRoot, "shared", "generated", "ui-contract.ts")
23322
24683
  )
23323
24684
  );
23324
- const generatedDir = path21.join(options.projectRoot, "test", "generated");
24685
+ const generatedDir = path22.join(options.projectRoot, "test", "generated");
23325
24686
  await ensureDir(generatedDir);
23326
24687
  const baseStates = {};
23327
24688
  const [gameModule, manifestContractModule] = await Promise.all([
23328
24689
  importTypeScriptModule(
23329
- path21.join(options.projectRoot, "app", "game.ts")
24690
+ path22.join(options.projectRoot, "app", "game.ts")
23330
24691
  ),
23331
24692
  importTypeScriptModule(
23332
- path21.join(options.projectRoot, "shared", "manifest-contract.ts")
24693
+ path22.join(options.projectRoot, "shared", "manifest-contract.ts")
23333
24694
  )
23334
24695
  ]);
23335
24696
  const createInitialTable = typeof manifestContractModule.createInitialTable === "function" ? manifestContractModule.createInitialTable : null;
@@ -23417,21 +24778,21 @@ async function writeReducerNativeGeneratedFiles(options) {
23417
24778
  }
23418
24779
  const header = "// Generated by dreamboard test generate. Do not edit by hand.\n";
23419
24780
  await writeTextFile(
23420
- path21.join(generatedDir, "base-states.generated.ts"),
24781
+ path22.join(generatedDir, "base-states.generated.ts"),
23421
24782
  `${header}export const BASE_STATES = ${JSON.stringify(baseStates, null, 2)} as const;
23422
24783
  `
23423
24784
  );
23424
24785
  await writeTextFile(
23425
- path21.join(generatedDir, "base-states.generated.d.ts"),
24786
+ path22.join(generatedDir, "base-states.generated.d.ts"),
23426
24787
  `${header}export declare const BASE_STATES: Record<string, unknown>;
23427
24788
  `
23428
24789
  );
23429
24790
  await writeTextFile(
23430
- path21.join(generatedDir, "scenario-manifest.generated.ts"),
24791
+ path22.join(generatedDir, "scenario-manifest.generated.ts"),
23431
24792
  `${header}export const SCENARIO_MANIFEST = ${JSON.stringify(
23432
24793
  options.scenarios.map((scenario) => ({
23433
24794
  id: scenario.definition.id,
23434
- filePath: path21.relative(generatedDir, scenario.filePath),
24795
+ filePath: path22.relative(generatedDir, scenario.filePath),
23435
24796
  base: scenario.definition.from,
23436
24797
  runners: normalizeScenarioRunners(scenario.definition.runners)
23437
24798
  })),
@@ -23440,14 +24801,14 @@ async function writeReducerNativeGeneratedFiles(options) {
23440
24801
  )} as const;
23441
24802
  `
23442
24803
  );
23443
- await writeJsonFile(path21.join(generatedDir, ".generation-meta.json"), {
24804
+ await writeJsonFile(path22.join(generatedDir, ".generation-meta.json"), {
23444
24805
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
23445
24806
  scenarioCount: options.scenarios.length,
23446
24807
  baseStateCount: options.bases.length
23447
24808
  });
23448
24809
  }
23449
24810
  async function loadGeneratedBaseStates(projectRoot) {
23450
- const filePath = path21.join(
24811
+ const filePath = path22.join(
23451
24812
  projectRoot,
23452
24813
  "test",
23453
24814
  "generated",
@@ -23460,7 +24821,7 @@ async function loadGeneratedBaseStates(projectRoot) {
23460
24821
  return module.BASE_STATES ?? null;
23461
24822
  }
23462
24823
  async function loadGeneratedScenarioManifest(projectRoot) {
23463
- const filePath = path21.join(
24824
+ const filePath = path22.join(
23464
24825
  projectRoot,
23465
24826
  "test",
23466
24827
  "generated",
@@ -23550,12 +24911,12 @@ async function currentFingerprint(options) {
23550
24911
  manifestHash: hashContent(JSON.stringify(manifest)),
23551
24912
  appBundleHash: hashContent(
23552
24913
  await bundleTypeScriptModuleText(
23553
- path21.join(options.projectRoot, "app", "game.ts")
24914
+ path22.join(options.projectRoot, "app", "game.ts")
23554
24915
  )
23555
24916
  ),
23556
24917
  uiContractHash: hashContent(
23557
24918
  await bundleTypeScriptModuleText(
23558
- path21.join(options.projectRoot, "shared", "generated", "ui-contract.ts")
24919
+ path22.join(options.projectRoot, "shared", "generated", "ui-contract.ts")
23559
24920
  )
23560
24921
  ),
23561
24922
  setupProfileId: effectiveSetup.setupProfileId,
@@ -23602,10 +24963,10 @@ async function runReducerNativeScenarios(options) {
23602
24963
  loadGeneratedBaseStates(options.projectRoot),
23603
24964
  loadManifest(options.projectRoot),
23604
24965
  importTypeScriptModule(
23605
- path21.join(options.projectRoot, "app", "game.ts")
24966
+ path22.join(options.projectRoot, "app", "game.ts")
23606
24967
  ),
23607
24968
  importTypeScriptModule(
23608
- path21.join(options.projectRoot, "shared", "manifest-contract.ts")
24969
+ path22.join(options.projectRoot, "shared", "manifest-contract.ts")
23609
24970
  )
23610
24971
  ]);
23611
24972
  let generatedBaseStates = initialGeneratedBaseStates;
@@ -23823,7 +25184,11 @@ async function runReducerNativeScenarios(options) {
23823
25184
  results.push({
23824
25185
  id: scenario.definition.id,
23825
25186
  success: false,
23826
- error: error instanceof Error ? error.message : `Scenario '${scenario.definition.id}' failed.`
25187
+ error: error instanceof Error ? formatScenarioErrorForDisplay({
25188
+ error,
25189
+ projectRoot: options.projectRoot,
25190
+ scenarioFilePath: scenario.filePath
25191
+ }) : `Scenario '${scenario.definition.id}' failed.`
23827
25192
  });
23828
25193
  }
23829
25194
  }
@@ -23839,8 +25204,8 @@ async function runReducerNativeScenarios(options) {
23839
25204
  }
23840
25205
 
23841
25206
  // src/services/project/reducer-contract-preflight.ts
23842
- import path22 from "path";
23843
- var GAME_CONTRACT_ENTRY_PATH = path22.join("app", "game-contract.ts");
25207
+ import path23 from "path";
25208
+ var GAME_CONTRACT_ENTRY_PATH = path23.join("app", "game-contract.ts");
23844
25209
  function normalizeErrorMessage(error) {
23845
25210
  const rawMessage = error instanceof Error ? error.message : String(error ?? "Unknown error");
23846
25211
  return rawMessage.split("\n").map((line) => line.trim()).find(Boolean)?.replace(/^Error:\s*/u, "") ?? "Unknown error";
@@ -23849,7 +25214,7 @@ function isManifestScopedIdBrandingError(message) {
23849
25214
  return message.includes("defineGameContract:") && message.includes("manifest-scoped") && (message.includes("uses a raw z.string()") || message.includes("uses z.array(z.string())"));
23850
25215
  }
23851
25216
  async function assertReducerContractPreflight(projectRoot) {
23852
- const entryPath = path22.join(projectRoot, GAME_CONTRACT_ENTRY_PATH);
25217
+ const entryPath = path23.join(projectRoot, GAME_CONTRACT_ENTRY_PATH);
23853
25218
  try {
23854
25219
  await importTypeScriptModule(entryPath);
23855
25220
  } catch (error) {
@@ -23875,8 +25240,8 @@ async function assertReducerContractPreflight(projectRoot) {
23875
25240
  }
23876
25241
 
23877
25242
  // src/services/project/reducer-bundle-preflight.ts
23878
- import path23 from "path";
23879
- var REDUCER_BUNDLE_ENTRY_PATH = path23.join("app", "index.ts");
25243
+ import path24 from "path";
25244
+ var REDUCER_BUNDLE_ENTRY_PATH = path24.join("app", "index.ts");
23880
25245
  var PREFLIGHT_RNG_SEED = 1337;
23881
25246
  function buildPlayerIds(playerCount) {
23882
25247
  const ids = [];
@@ -24064,7 +25429,7 @@ async function runReducerBundleSmoke(options) {
24064
25429
  if (scenarios.length === 0) {
24065
25430
  return [];
24066
25431
  }
24067
- const entryPath = path23.join(projectRoot, REDUCER_BUNDLE_ENTRY_PATH);
25432
+ const entryPath = path24.join(projectRoot, REDUCER_BUNDLE_ENTRY_PATH);
24068
25433
  let module;
24069
25434
  try {
24070
25435
  module = await importTypeScriptModule(
@@ -24249,9 +25614,9 @@ var dev_default = defineCommand({
24249
25614
  env: parsedArgs.env ?? "dev",
24250
25615
  debug: parsedArgs.debug
24251
25616
  });
24252
- const devDir = path24.join(projectRoot, PROJECT_DIR_NAME, "dev");
25617
+ const devDir = path25.join(projectRoot, PROJECT_DIR_NAME, "dev");
24253
25618
  await ensureDir(devDir);
24254
- const sessionFilePath = path24.join(devDir, "session.json");
25619
+ const sessionFilePath = path25.join(devDir, "session.json");
24255
25620
  const requestedResumeSessionId = parsedArgs.resume?.trim() || null;
24256
25621
  const requestedScenarioId = parsedArgs["from-scenario"]?.trim() || null;
24257
25622
  if (requestedResumeSessionId && parsedArgs["new-session"]) {
@@ -24431,26 +25796,32 @@ async function ensureDevCompiledResult(options) {
24431
25796
  const localMaintainerEnabled = isLocalMaintainerRegistryEnabled(
24432
25797
  options.config.apiBaseUrl
24433
25798
  );
24434
- const localMaintainerRegistry = localMaintainerEnabled ? await runLoggedStep(
25799
+ const existingLocalMaintainerRegistry = getProjectLocalMaintainerRegistry(
25800
+ options.projectConfig
25801
+ );
25802
+ const refreshedLocalMaintainerRegistry = localMaintainerEnabled ? await runLoggedStep(
24435
25803
  "Checking local SDK snapshot...",
24436
25804
  () => ensureLocalMaintainerSnapshot(options.config.apiBaseUrl)
24437
25805
  ) : await ensureLocalMaintainerSnapshot(options.config.apiBaseUrl);
24438
- if (localMaintainerRegistry) {
25806
+ const localMaintainerRegistry = refreshedLocalMaintainerRegistry ?? (localMaintainerEnabled ? existingLocalMaintainerRegistry ?? null : null);
25807
+ if (refreshedLocalMaintainerRegistry) {
24439
25808
  if (didLocalMaintainerSnapshotChange(
24440
- getProjectLocalMaintainerRegistry(options.projectConfig),
24441
- localMaintainerRegistry
25809
+ existingLocalMaintainerRegistry,
25810
+ refreshedLocalMaintainerRegistry
24442
25811
  )) {
24443
25812
  await updateProjectState(
24444
25813
  options.projectRoot,
24445
25814
  updateProjectLocalMaintainerRegistry(
24446
25815
  options.projectConfig,
24447
- localMaintainerRegistry
25816
+ refreshedLocalMaintainerRegistry
24448
25817
  )
24449
25818
  );
24450
25819
  consola.info("Local SDK snapshot refreshed.");
24451
25820
  } else {
24452
25821
  consola.info("Using existing local SDK snapshot.");
24453
25822
  }
25823
+ } else if (localMaintainerRegistry) {
25824
+ consola.info("Using workspace-pinned local SDK snapshot.");
24454
25825
  }
24455
25826
  await runLoggedStep(
24456
25827
  "Refreshing static scaffold...",
@@ -24586,7 +25957,7 @@ async function resolveBackendVersionMetadata() {
24586
25957
  }
24587
25958
  async function readWorkspacePackageJson(projectRoot) {
24588
25959
  try {
24589
- const raw = await readTextFile(path24.join(projectRoot, "package.json"));
25960
+ const raw = await readTextFile(path25.join(projectRoot, "package.json"));
24590
25961
  const parsed = JSON.parse(raw);
24591
25962
  return {
24592
25963
  dependencies: Object.fromEntries(
@@ -24724,7 +26095,7 @@ function extractUserIdFromJwt(token) {
24724
26095
  }
24725
26096
 
24726
26097
  // src/commands/join.ts
24727
- import path25 from "path";
26098
+ import path26 from "path";
24728
26099
 
24729
26100
  // src/services/join/jsonl-bot-session.ts
24730
26101
  import { createInterface as createInterface2 } from "readline";
@@ -25104,17 +26475,20 @@ function actionCacheFromGameplay(gameplay) {
25104
26475
  };
25105
26476
  }
25106
26477
  function compactActionFromDescriptor(descriptor) {
26478
+ const availability = descriptor.availability;
26479
+ const unavailableReason = availability.status === "available" ? void 0 : availability.reason;
26480
+ const missingResources = availability.status === "insufficientResources" ? availability.missingResources : void 0;
25107
26481
  return compactObject({
25108
26482
  interactionId: descriptor.interactionId,
25109
26483
  interactionKey: descriptor.interactionKey,
25110
26484
  kind: descriptor.kind,
25111
- available: descriptor.available ?? false,
25112
- unavailableReason: descriptor.unavailableReason,
26485
+ available: availability.status === "available",
26486
+ unavailableReason,
25113
26487
  inputs: normalizeUnknownJson(descriptor.inputs),
25114
26488
  cost: normalizeUnknownJson(descriptor.cost),
25115
26489
  currentResources: normalizeUnknownJson(descriptor.currentResources),
25116
- missingResources: descriptor.missingResources,
25117
- context: normalizeUnknownJson(descriptor.context)
26490
+ missingResources,
26491
+ context: descriptor.kind === "prompt" ? normalizeUnknownJson(descriptor.context) : void 0
25118
26492
  });
25119
26493
  }
25120
26494
  function compactGameplayEvent(options) {
@@ -25646,7 +27020,7 @@ async function resolveDefaultSessionId(projectRoot) {
25646
27020
  "No session id provided and this directory is not a Dreamboard project. Pass --session or run from a workspace with .dreamboard/dev/session.json."
25647
27021
  );
25648
27022
  }
25649
- const sessionFilePath = path25.join(
27023
+ const sessionFilePath = path26.join(
25650
27024
  projectRoot,
25651
27025
  PROJECT_DIR_NAME,
25652
27026
  "dev",
@@ -25758,7 +27132,7 @@ var logout_default = defineCommand({
25758
27132
  });
25759
27133
 
25760
27134
  // src/commands/new.ts
25761
- import path26 from "path";
27135
+ import path27 from "path";
25762
27136
  var new_default = defineCommand({
25763
27137
  meta: {
25764
27138
  name: "new",
@@ -25879,7 +27253,7 @@ var new_default = defineCommand({
25879
27253
  ruleId
25880
27254
  );
25881
27255
  consola.start("Scaffolding local workspace...");
25882
- const targetDir = path26.resolve(process.cwd(), normalizedSlug);
27256
+ const targetDir = path27.resolve(process.cwd(), normalizedSlug);
25883
27257
  await ensureDir(targetDir);
25884
27258
  await writeManifest(targetDir, blankManifest);
25885
27259
  await writeRule(targetDir, "");
@@ -26104,7 +27478,8 @@ var status_default = defineCommand({
26104
27478
  let remoteHeadId = null;
26105
27479
  let authoredRelation = "unknown";
26106
27480
  let compileRelation = "never_compiled";
26107
- let completionLights = null;
27481
+ let verified = null;
27482
+ let verifiedAt = null;
26108
27483
  if (config.authToken) {
26109
27484
  await configureClient(config);
26110
27485
  const remoteHead = await getAuthoringHeadSdk(projectConfig.gameId);
@@ -26113,7 +27488,8 @@ var status_default = defineCommand({
26113
27488
  path: { gameId: projectConfig.gameId }
26114
27489
  });
26115
27490
  if (game) {
26116
- completionLights = game.completionLights;
27491
+ verified = game.verified;
27492
+ verifiedAt = game.verifiedAt ?? null;
26117
27493
  }
26118
27494
  const hasLocalDiff = diff.modified.length > 0 || diff.added.length > 0 || diff.deleted.length > 0;
26119
27495
  if (pendingSync) {
@@ -26168,7 +27544,10 @@ var status_default = defineCommand({
26168
27544
  deleted: diff.deleted.length
26169
27545
  },
26170
27546
  localDiffPaths: diff,
26171
- completionLights
27547
+ verification: {
27548
+ verified,
27549
+ verifiedAt
27550
+ }
26172
27551
  },
26173
27552
  null,
26174
27553
  2
@@ -26184,14 +27563,10 @@ var status_default = defineCommand({
26184
27563
  if (diff.added.length > 0) consola.log(`Added: ${diff.added.join(", ")}`);
26185
27564
  if (diff.deleted.length > 0)
26186
27565
  consola.log(`Deleted: ${diff.deleted.join(", ")}`);
26187
- if (completionLights) {
26188
- const lightSummary = [
26189
- `rules:${completionLights.rules}`,
26190
- `manifest:${completionLights.manifest}`,
26191
- `phases:${completionLights.phases}`,
26192
- `ui:${completionLights.ui}`
26193
- ].join(" ");
26194
- consola.info(`Completion lights: ${lightSummary}`);
27566
+ if (verified !== null) {
27567
+ consola.info(
27568
+ `Verification: ${verified ? "verified" : "unverified"}${verifiedAt ? ` at ${verifiedAt}` : ""}`
27569
+ );
26195
27570
  }
26196
27571
  if (!config.authToken) {
26197
27572
  consola.warn("Remote status unavailable (no auth token).");
@@ -26235,9 +27610,9 @@ function buildSourceChanges(options) {
26235
27610
  if (mode === "replace") {
26236
27611
  return Object.entries(localFiles).filter(
26237
27612
  ([filePath]) => isSourceRevisionPath(filePath) || shouldAlwaysUpsertSourcePath(filePath)
26238
- ).sort(([left], [right]) => left.localeCompare(right)).map(([path28, content]) => ({
27613
+ ).sort(([left], [right]) => left.localeCompare(right)).map(([path29, content]) => ({
26239
27614
  kind: "upsert",
26240
- path: path28,
27615
+ path: path29,
26241
27616
  content
26242
27617
  }));
26243
27618
  }
@@ -26404,22 +27779,26 @@ var sync_default = defineCommand({
26404
27779
  const localMaintainerEnabled = isLocalMaintainerRegistryEnabled(
26405
27780
  config.apiBaseUrl
26406
27781
  );
26407
- const localMaintainerRegistry = localMaintainerEnabled ? await runLoggedStep2(
27782
+ const existingLocalMaintainerRegistry = getProjectLocalMaintainerRegistry(projectConfig);
27783
+ const refreshedLocalMaintainerRegistry = localMaintainerEnabled ? await runLoggedStep2(
26408
27784
  "Checking local SDK snapshot...",
26409
27785
  () => ensureLocalMaintainerSnapshot(config.apiBaseUrl)
26410
27786
  ) : await ensureLocalMaintainerSnapshot(config.apiBaseUrl);
27787
+ const localMaintainerRegistry = refreshedLocalMaintainerRegistry ?? (localMaintainerEnabled ? existingLocalMaintainerRegistry ?? null : null);
26411
27788
  const localMaintainerSnapshotChanged = didLocalMaintainerSnapshotChange(
26412
- getProjectLocalMaintainerRegistry(projectConfig),
26413
- localMaintainerRegistry
27789
+ existingLocalMaintainerRegistry,
27790
+ refreshedLocalMaintainerRegistry
26414
27791
  );
26415
- if (localMaintainerRegistry) {
27792
+ if (refreshedLocalMaintainerRegistry) {
26416
27793
  nextProjectConfig = updateProjectLocalMaintainerRegistry(
26417
27794
  nextProjectConfig,
26418
- localMaintainerRegistry
27795
+ refreshedLocalMaintainerRegistry
26419
27796
  );
26420
27797
  consola.info(
26421
27798
  localMaintainerSnapshotChanged ? "Local SDK snapshot refreshed." : "Using existing local SDK snapshot."
26422
27799
  );
27800
+ } else if (localMaintainerRegistry) {
27801
+ consola.info("Using workspace-pinned local SDK snapshot.");
26423
27802
  }
26424
27803
  await runLoggedStep2(
26425
27804
  "Refreshing static scaffold...",
@@ -26791,7 +28170,7 @@ var sync_default = defineCommand({
26791
28170
 
26792
28171
  // src/commands/test.ts
26793
28172
  import { readFile as readFile4 } from "fs/promises";
26794
- import path27 from "path";
28173
+ import path28 from "path";
26795
28174
 
26796
28175
  // src/services/testing/runtime-mode.ts
26797
28176
  function isRemoteTestEnvironment(environment) {
@@ -26855,7 +28234,7 @@ async function uploadGeneratedPreviewProjection(options) {
26855
28234
  if (!previewBase) {
26856
28235
  return;
26857
28236
  }
26858
- const projectionPath = path27.join(
28237
+ const projectionPath = path28.join(
26859
28238
  options.projectRoot,
26860
28239
  "test",
26861
28240
  "generated",
@@ -27115,7 +28494,7 @@ function runDreamboardCli(internalSubCommands = {}) {
27115
28494
  const main = defineCommand({
27116
28495
  meta: {
27117
28496
  name: "dreamboard",
27118
- version: "0.1.24",
28497
+ version: "0.1.26",
27119
28498
  description: "Dreamboard CLI \u2014 game development platform"
27120
28499
  },
27121
28500
  subCommands
@@ -27171,4 +28550,4 @@ export {
27171
28550
  test_default,
27172
28551
  runDreamboardCli
27173
28552
  };
27174
- //# sourceMappingURL=chunk-GL46CLDI.js.map
28553
+ //# sourceMappingURL=chunk-VRJ4FOH2.js.map