@momoyu-ink/kit 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib.mjs CHANGED
@@ -19,6 +19,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
19
19
  // src/lib.ts
20
20
  var lib_exports = {};
21
21
  __export(lib_exports, {
22
+ DEBUG_EMPTY_PAGE: () => DEBUG_EMPTY_PAGE,
22
23
  OverlayParamsContext: () => OverlayParamsContext,
23
24
  PageParamsContext: () => PageParamsContext,
24
25
  StageContextProvider: () => StageContextProvider,
@@ -31,6 +32,7 @@ __export(lib_exports, {
31
32
  createStackNavigator: () => createStackNavigator,
32
33
  createStage: () => createStage,
33
34
  createStaticNavigation: () => createStaticNavigation,
35
+ ensureArchiveVariableDefaults: () => ensureArchiveVariableDefaults,
34
36
  executeNodeCommand: () => executeNodeCommand,
35
37
  executePluginCommand: () => executePluginCommand,
36
38
  fastForwardState: () => fastForwardState,
@@ -38,11 +40,16 @@ __export(lib_exports, {
38
40
  getNavigator: () => getNavigator,
39
41
  getSeekingType: () => getSeekingType,
40
42
  getStageSize: () => getStageSize,
43
+ getUiData: () => getUiData,
41
44
  globalEventListeners: () => globalEventListeners,
42
45
  globalRequestAnimationFrameListeners: () => globalRequestAnimationFrameListeners,
46
+ initGlobalVariableDefaults: () => initGlobalVariableDefaults,
47
+ initUiData: () => initUiData,
48
+ loadVariablesDocument: () => loadVariablesDocument,
43
49
  moyu: () => moyu_exports,
44
50
  nextLine: () => nextLine,
45
51
  registerAppStateAdapter: () => registerAppStateAdapter,
52
+ replaceUiData: () => replaceUiData,
46
53
  setDefaultAutoTailMs: () => setDefaultAutoTailMs,
47
54
  setWaiting: () => setWaiting,
48
55
  skipState: () => skipState,
@@ -69,6 +76,7 @@ __export(lib_exports, {
69
76
  useSkipCallback: () => useSkipCallback,
70
77
  useSoundEffect: () => useSoundEffect,
71
78
  useStageContext: () => useStageContext,
79
+ useUiData: () => useUiData,
72
80
  warpState: () => warpState
73
81
  });
74
82
 
@@ -393,6 +401,259 @@ function createRoot(options) {
393
401
  };
394
402
  }
395
403
 
404
+ // src/variables.ts
405
+ var VARIABLES_DATA_ASSET_PATH = "assets:///data/variables.json";
406
+ function normalizeVariableDefinitions(value) {
407
+ if (!Array.isArray(value)) {
408
+ return [];
409
+ }
410
+ return value.flatMap((entry) => {
411
+ if (!entry || typeof entry !== "object") {
412
+ return [];
413
+ }
414
+ const definition = entry;
415
+ if (typeof definition.id !== "string" || definition.id.length === 0) {
416
+ return [];
417
+ }
418
+ return [{ id: definition.id, defaultValue: definition.defaultValue }];
419
+ });
420
+ }
421
+ async function loadVariablesDocument() {
422
+ let rawText;
423
+ try {
424
+ rawText = await executePluginCommand("system", {
425
+ subCommand: "readFile",
426
+ path: VARIABLES_DATA_ASSET_PATH
427
+ });
428
+ } catch {
429
+ return null;
430
+ }
431
+ let rawData;
432
+ try {
433
+ rawData = JSON.parse(rawText);
434
+ } catch (error) {
435
+ console.error("Failed to parse variable defaults data:", error);
436
+ return null;
437
+ }
438
+ if (!rawData || typeof rawData !== "object") {
439
+ return {
440
+ archive: [],
441
+ global: []
442
+ };
443
+ }
444
+ const document = rawData;
445
+ return {
446
+ archive: normalizeVariableDefinitions(document.archive),
447
+ global: normalizeVariableDefinitions(document.global)
448
+ };
449
+ }
450
+ function toVariableRecord(value) {
451
+ if (value instanceof Map) {
452
+ return Object.fromEntries(value);
453
+ }
454
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
455
+ return {};
456
+ }
457
+ return value;
458
+ }
459
+ async function ensureVariableDefaults(scope) {
460
+ const document = await loadVariablesDocument();
461
+ if (!document) {
462
+ return;
463
+ }
464
+ const definitions = document[scope];
465
+ if (definitions.length === 0) {
466
+ return;
467
+ }
468
+ const currentVariables = toVariableRecord(
469
+ await executePluginCommand(
470
+ "scenario",
471
+ scope === "global" ? { subCommand: "getPermanentVariables" } : { subCommand: "getVariables" }
472
+ )
473
+ );
474
+ const missingVariables = {};
475
+ for (const definition of definitions) {
476
+ if (!Object.hasOwn(currentVariables, definition.id)) {
477
+ missingVariables[definition.id] = definition.defaultValue;
478
+ }
479
+ }
480
+ if (Object.keys(missingVariables).length === 0) {
481
+ return;
482
+ }
483
+ await executePluginCommand(
484
+ "scenario",
485
+ scope === "global" ? {
486
+ subCommand: "setPermanentVariables",
487
+ variables: missingVariables
488
+ } : {
489
+ subCommand: "setVariables",
490
+ variables: missingVariables
491
+ }
492
+ );
493
+ reportRuntimeDebugVariablesSet(scope, missingVariables);
494
+ }
495
+ async function initGlobalVariableDefaults() {
496
+ await ensureVariableDefaults("global");
497
+ }
498
+ async function ensureArchiveVariableDefaults() {
499
+ await ensureVariableDefaults("archive");
500
+ }
501
+
502
+ // src/debug/variableMonitor.ts
503
+ var sendRuntimeDebugVariableMessage = null;
504
+ var allowlistCache = null;
505
+ var allowlistPromise = null;
506
+ var variableEmissionSuspended = false;
507
+ function createEmptyAllowlist() {
508
+ return {
509
+ archive: /* @__PURE__ */ new Set(),
510
+ global: /* @__PURE__ */ new Set()
511
+ };
512
+ }
513
+ function isJsonValue(value) {
514
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
515
+ return true;
516
+ }
517
+ if (Array.isArray(value)) {
518
+ return value.every((item) => isJsonValue(item));
519
+ }
520
+ if (!value || typeof value !== "object") {
521
+ return false;
522
+ }
523
+ return Object.values(value).every((item) => isJsonValue(item));
524
+ }
525
+ async function loadRuntimeDebugVariableAllowlist() {
526
+ const document = await loadVariablesDocument();
527
+ if (!document) {
528
+ return createEmptyAllowlist();
529
+ }
530
+ return {
531
+ archive: new Set(document.archive.map((def) => def.id)),
532
+ global: new Set(document.global.map((def) => def.id))
533
+ };
534
+ }
535
+ function toJsonValueRecord(value) {
536
+ if (value instanceof Map) {
537
+ return Object.fromEntries(
538
+ Array.from(value.entries()).flatMap(([key, entryValue]) => {
539
+ if (typeof key !== "string" || !isJsonValue(entryValue)) {
540
+ return [];
541
+ }
542
+ return [[key, entryValue]];
543
+ })
544
+ );
545
+ }
546
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
547
+ return {};
548
+ }
549
+ return Object.fromEntries(
550
+ Object.entries(value).flatMap(([key, entryValue]) => {
551
+ if (!isJsonValue(entryValue)) {
552
+ return [];
553
+ }
554
+ return [[key, entryValue]];
555
+ })
556
+ );
557
+ }
558
+ function filterVariablesByAllowlist(scope, values, allowlist) {
559
+ const allowedIds = allowlist[scope];
560
+ if (allowedIds.size === 0) {
561
+ return {};
562
+ }
563
+ return Object.fromEntries(
564
+ Object.entries(values).flatMap(([key, value]) => allowedIds.has(key) ? [[key, value]] : [])
565
+ );
566
+ }
567
+ function hasRecordEntries(value) {
568
+ return !!value && Object.keys(value).length > 0;
569
+ }
570
+ async function ensureRuntimeDebugVariableAllowlist() {
571
+ if (allowlistCache) {
572
+ return allowlistCache;
573
+ }
574
+ if (!allowlistPromise) {
575
+ allowlistPromise = loadRuntimeDebugVariableAllowlist().then((allowlist) => {
576
+ allowlistCache = allowlist;
577
+ return allowlist;
578
+ }).finally(() => {
579
+ allowlistPromise = null;
580
+ });
581
+ }
582
+ return allowlistPromise;
583
+ }
584
+ async function emitRuntimeDebugVariablesChanged(scope, variables) {
585
+ const sender = sendRuntimeDebugVariableMessage;
586
+ if (!sender || variableEmissionSuspended) {
587
+ return;
588
+ }
589
+ const allowlist = await ensureRuntimeDebugVariableAllowlist();
590
+ if (!sendRuntimeDebugVariableMessage || sendRuntimeDebugVariableMessage !== sender || variableEmissionSuspended) {
591
+ return;
592
+ }
593
+ const filtered = filterVariablesByAllowlist(scope, variables, allowlist);
594
+ if (!hasRecordEntries(filtered)) {
595
+ return;
596
+ }
597
+ sender({
598
+ type: "variables:changed",
599
+ archiveSet: scope === "archive" ? filtered : void 0,
600
+ globalSet: scope === "global" ? filtered : void 0
601
+ });
602
+ }
603
+ function setRuntimeDebugVariableMessageSender(sender) {
604
+ sendRuntimeDebugVariableMessage = sender;
605
+ if (sender) {
606
+ void ensureRuntimeDebugVariableAllowlist();
607
+ }
608
+ }
609
+ function setRuntimeDebugVariableEmissionSuspended(suspended) {
610
+ variableEmissionSuspended = suspended;
611
+ }
612
+ function resetRuntimeDebugVariableMonitor() {
613
+ sendRuntimeDebugVariableMessage = null;
614
+ allowlistCache = null;
615
+ allowlistPromise = null;
616
+ variableEmissionSuspended = false;
617
+ }
618
+ function reportRuntimeDebugVariableSet(scope, key, value) {
619
+ if (key.length === 0) {
620
+ return;
621
+ }
622
+ void emitRuntimeDebugVariablesChanged(scope, {
623
+ [key]: value
624
+ });
625
+ }
626
+ function reportRuntimeDebugVariablesSet(scope, variables) {
627
+ if (Object.keys(variables).length === 0) {
628
+ return;
629
+ }
630
+ void emitRuntimeDebugVariablesChanged(scope, variables);
631
+ }
632
+ async function syncRuntimeDebugVariablesSnapshot(reason) {
633
+ const sender = sendRuntimeDebugVariableMessage;
634
+ if (!sender) {
635
+ return;
636
+ }
637
+ const [allowlist, archiveVariables2, globalVariables2] = await Promise.all([
638
+ ensureRuntimeDebugVariableAllowlist(),
639
+ executePluginCommand("scenario", {
640
+ subCommand: "getVariables"
641
+ }),
642
+ executePluginCommand("scenario", {
643
+ subCommand: "getPermanentVariables"
644
+ })
645
+ ]);
646
+ if (!sendRuntimeDebugVariableMessage || sendRuntimeDebugVariableMessage !== sender) {
647
+ return;
648
+ }
649
+ sender({
650
+ type: "variables:snapshot",
651
+ reason,
652
+ archive: filterVariablesByAllowlist("archive", toJsonValueRecord(archiveVariables2), allowlist),
653
+ global: filterVariablesByAllowlist("global", toJsonValueRecord(globalVariables2), allowlist)
654
+ });
655
+ }
656
+
396
657
  // src/sandbox.ts
397
658
  var JS_GLOBAL_KEYS = new Set(Object.getOwnPropertyNames(globalThis));
398
659
  var JS_GLOBAL_KEYS_EXCLUDED = /* @__PURE__ */ new Set(["window", "globalThis", "LOCAL", "ARCHIVE", "GLOBAL"]);
@@ -443,6 +704,7 @@ var archiveVariables = new Proxy(
443
704
  name: key,
444
705
  value
445
706
  });
707
+ reportRuntimeDebugVariableSet("archive", key, value);
446
708
  return true;
447
709
  }
448
710
  return false;
@@ -468,6 +730,7 @@ var globalVariables = new Proxy(
468
730
  key,
469
731
  value
470
732
  });
733
+ reportRuntimeDebugVariableSet("global", key, value);
471
734
  return true;
472
735
  }
473
736
  return false;
@@ -475,7 +738,7 @@ var globalVariables = new Proxy(
475
738
  }
476
739
  );
477
740
  function safeEval(code, sandbox) {
478
- const proxy4 = new Proxy(sandbox, {
741
+ const proxy5 = new Proxy(sandbox, {
479
742
  has(target, key) {
480
743
  if (typeof key === "string" && JS_GLOBAL_KEYS.has(key)) {
481
744
  return false;
@@ -483,7 +746,7 @@ function safeEval(code, sandbox) {
483
746
  return key in target || key === "LOCAL" || key === "ARCHIVE" || key === "GLOBAL" || key === "window" || key === "globalThis";
484
747
  },
485
748
  get(target, key) {
486
- if (key === "window" || key === "globalThis") return proxy4;
749
+ if (key === "window" || key === "globalThis") return proxy5;
487
750
  if (key === "LOCAL") return localVariables;
488
751
  if (key === "ARCHIVE") return archiveVariables;
489
752
  if (key === "GLOBAL") return globalVariables;
@@ -495,7 +758,7 @@ function safeEval(code, sandbox) {
495
758
  return true;
496
759
  }
497
760
  });
498
- return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy4)(proxy4);
761
+ return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy5)(proxy5);
499
762
  }
500
763
  globalThis.__moyu_eval_sandbox = (code) => {
501
764
  try {
@@ -683,6 +946,7 @@ import { proxy, useSnapshot } from "valtio";
683
946
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
684
947
  var PageParamsContext = createContext(void 0);
685
948
  var OverlayParamsContext = createContext(void 0);
949
+ var DEBUG_EMPTY_PAGE = "debug-empty";
686
950
  var overlayIdCounter = 0;
687
951
  var generateOverlayId = () => {
688
952
  return `overlay-${++overlayIdCounter}-${Date.now()}`;
@@ -710,6 +974,12 @@ function createStackNavigator(options) {
710
974
  for (const [key, config] of Object.entries(options.pages)) {
711
975
  normalizedPages[key] = normalizeDescriptor(config);
712
976
  }
977
+ if (normalizedPages[DEBUG_EMPTY_PAGE]) {
978
+ throw new Error(`Navigation Error: Page name "${DEBUG_EMPTY_PAGE}" is reserved.`);
979
+ }
980
+ normalizedPages[DEBUG_EMPTY_PAGE] = {
981
+ component: () => null
982
+ };
713
983
  const normalizedOverlays = {};
714
984
  if (options.overlays) {
715
985
  for (const [key, config] of Object.entries(options.overlays)) {
@@ -739,6 +1009,9 @@ function createStackNavigator(options) {
739
1009
  hasPage: (page) => {
740
1010
  return normalizedPages[page] !== void 0;
741
1011
  },
1012
+ hasOverlay: (overlay) => {
1013
+ return normalizedOverlays[overlay] !== void 0;
1014
+ },
742
1015
  goBack: () => {
743
1016
  console.warn("goBack() not implemented yet");
744
1017
  },
@@ -826,6 +1099,7 @@ var debugState = proxy2({
826
1099
  active: false,
827
1100
  checkpoints: {},
828
1101
  currentCursor: null,
1102
+ currentStory: null,
829
1103
  restoring: false,
830
1104
  lastError: null
831
1105
  });
@@ -869,9 +1143,37 @@ function reportDebugSessionError(error) {
869
1143
  function clearLocalRuntimeState() {
870
1144
  debugState.checkpoints = {};
871
1145
  debugState.currentCursor = null;
1146
+ debugState.currentStory = null;
872
1147
  debugState.restoring = false;
873
1148
  debugState.lastError = null;
874
1149
  }
1150
+ async function restartJumpStoryFromHead(targetStory, currentStory) {
1151
+ debugState.checkpoints = {};
1152
+ debugState.currentCursor = null;
1153
+ debugState.currentStory = null;
1154
+ if (targetStory) {
1155
+ if (appStateAdapter?.restartStoryFromHead) {
1156
+ await appStateAdapter.restartStoryFromHead({
1157
+ story: targetStory,
1158
+ entry: "entry"
1159
+ });
1160
+ } else if (currentStory === targetStory && appStateAdapter?.restartCurrentStoryFromHead) {
1161
+ await appStateAdapter.restartCurrentStoryFromHead();
1162
+ } else {
1163
+ throw new Error("Restarting the target story from head is not supported by the app state adapter");
1164
+ }
1165
+ debugState.currentCursor = await executeScenarioCommand({
1166
+ subCommand: "getExecutionCursor"
1167
+ });
1168
+ debugState.currentStory = debugState.currentCursor?.story ?? targetStory;
1169
+ return;
1170
+ }
1171
+ if (!appStateAdapter?.restartCurrentStoryFromHead) {
1172
+ throw new Error("Restarting the current story from head is not supported by the app state adapter");
1173
+ }
1174
+ await appStateAdapter.restartCurrentStoryFromHead();
1175
+ debugState.currentCursor = null;
1176
+ }
875
1177
  async function doSettleActiveSeek(op, clearSlot, error) {
876
1178
  if (!op) {
877
1179
  return;
@@ -900,6 +1202,11 @@ async function settleActiveWarp(error) {
900
1202
  activeWarp = null;
901
1203
  }, error);
902
1204
  }
1205
+ async function clearLocalDebugSessionRuntimeState() {
1206
+ await settleActiveFastForward(new Error("Debug session runtime state cleared"));
1207
+ await settleActiveWarp(new Error("Debug session runtime state cleared"));
1208
+ clearLocalRuntimeState();
1209
+ }
903
1210
  async function clearRemoteCheckpoints() {
904
1211
  await executeScenarioCommand({ subCommand: "clearCheckpoints" });
905
1212
  }
@@ -924,6 +1231,7 @@ async function syncLocalWarpCheckpoint(targetMarkerId) {
924
1231
  appState: appStateAdapter ? await appStateAdapter.capture() : void 0
925
1232
  };
926
1233
  debugState.currentCursor = checkpointCursor;
1234
+ debugState.currentStory = checkpointCursor.story;
927
1235
  debugState.checkpoints[targetMarkerId] = checkpoint;
928
1236
  await currentConfig.onMarkerEnter(checkpoint);
929
1237
  }
@@ -947,9 +1255,16 @@ function notifyMarkerEnter(checkpoint) {
947
1255
  reportDebugSessionError(error);
948
1256
  }
949
1257
  }
950
- async function prepareJumpStart(markerId) {
1258
+ async function prepareJumpStart(markerId, options) {
1259
+ const currentStory = debugState.currentStory ?? debugState.currentCursor?.story ?? null;
1260
+ const targetStory = options?.story;
1261
+ let restartedFromHead = false;
1262
+ if (targetStory && targetStory !== currentStory) {
1263
+ await restartJumpStoryFromHead(targetStory, currentStory);
1264
+ restartedFromHead = true;
1265
+ }
951
1266
  const checkpoint = debugState.checkpoints[markerId];
952
- if (checkpoint) {
1267
+ if (!restartedFromHead && checkpoint && (!targetStory || checkpoint.cursor.story === targetStory)) {
953
1268
  const restored2 = await executeScenarioCommand({
954
1269
  subCommand: "restoreCheckpoint",
955
1270
  key: markerId
@@ -961,6 +1276,7 @@ async function prepareJumpStart(markerId) {
961
1276
  await appStateAdapter.restore(checkpoint.appState);
962
1277
  }
963
1278
  debugState.currentCursor = checkpoint.cursor;
1279
+ debugState.currentStory = checkpoint.cursor.story;
964
1280
  return false;
965
1281
  }
966
1282
  const lookup = await executeScenarioCommand({
@@ -968,16 +1284,15 @@ async function prepareJumpStart(markerId) {
968
1284
  key: markerId
969
1285
  });
970
1286
  if (!lookup.markerExists) {
971
- throw new Error(`Marker ${markerId} is not available in the current story`);
1287
+ throw new Error(
1288
+ targetStory ? `Marker ${markerId} is not available in story ${targetStory}` : `Marker ${markerId} is not available in the current story`
1289
+ );
972
1290
  }
973
1291
  const startCheckpointKey = lookup.checkpointKey ?? null;
974
1292
  if (!startCheckpointKey) {
975
- if (!appStateAdapter?.restartCurrentStoryFromHead) {
976
- throw new Error("Restarting the current story from head is not supported by the app state adapter");
1293
+ if (!restartedFromHead) {
1294
+ await restartJumpStoryFromHead(targetStory, currentStory);
977
1295
  }
978
- await appStateAdapter.restartCurrentStoryFromHead();
979
- debugState.checkpoints = {};
980
- debugState.currentCursor = null;
981
1296
  return true;
982
1297
  }
983
1298
  const startCheckpoint = debugState.checkpoints[startCheckpointKey];
@@ -995,6 +1310,7 @@ async function prepareJumpStart(markerId) {
995
1310
  await appStateAdapter.restore(startCheckpoint.appState);
996
1311
  }
997
1312
  debugState.currentCursor = startCheckpoint.cursor;
1313
+ debugState.currentStory = startCheckpoint.cursor.story;
998
1314
  return true;
999
1315
  }
1000
1316
  function handleMarkerEnter(marker) {
@@ -1007,6 +1323,7 @@ function handleMarkerEnter(marker) {
1007
1323
  appState: captureCheckpointAppStateSync()
1008
1324
  };
1009
1325
  debugState.currentCursor = checkpoint.cursor;
1326
+ debugState.currentStory = checkpoint.cursor.story;
1010
1327
  debugState.checkpoints[marker.markerId] = checkpoint;
1011
1328
  notifyMarkerEnter(checkpoint);
1012
1329
  if (activeFastForward?.targetMarkerId === marker.markerId) {
@@ -1067,11 +1384,11 @@ async function runWarp(options) {
1067
1384
  await completion;
1068
1385
  }
1069
1386
  var debugSessionController = {
1070
- async restoreCheckpoint(markerId) {
1387
+ async restoreCheckpoint(markerId, options) {
1071
1388
  debugState.restoring = true;
1072
1389
  debugState.lastError = null;
1073
1390
  try {
1074
- const needsFastForward = await enqueueDebugOperation(async () => prepareJumpStart(markerId));
1391
+ const needsFastForward = await enqueueDebugOperation(async () => prepareJumpStart(markerId, options));
1075
1392
  if (needsFastForward) {
1076
1393
  await runFastForwardToMarker(markerId);
1077
1394
  }
@@ -1087,7 +1404,7 @@ var debugSessionController = {
1087
1404
  debugState.restoring = true;
1088
1405
  debugState.lastError = null;
1089
1406
  try {
1090
- const needsWarp = await enqueueDebugOperation(async () => prepareJumpStart(options.markerId));
1407
+ const needsWarp = await enqueueDebugOperation(async () => prepareJumpStart(options.markerId, options));
1091
1408
  if (needsWarp) {
1092
1409
  await runWarp(options);
1093
1410
  }
@@ -1098,14 +1415,26 @@ var debugSessionController = {
1098
1415
  debugState.restoring = false;
1099
1416
  }
1100
1417
  },
1101
- async switchPage(page, params) {
1418
+ async switchRoute(routeName, params) {
1102
1419
  return enqueueDebugOperation(async () => {
1103
1420
  debugState.lastError = null;
1104
1421
  try {
1105
- if (!appStateAdapter?.switchPage) {
1106
- throw new Error("Page switching is not supported by the app state adapter");
1422
+ const navigator = getNavigator();
1423
+ if (navigator.hasPage(routeName)) {
1424
+ if (!appStateAdapter?.switchPage) {
1425
+ throw new Error("Page switching is not supported by the app state adapter");
1426
+ }
1427
+ await appStateAdapter.switchPage(routeName, params);
1428
+ return;
1429
+ }
1430
+ if (navigator.hasOverlay(routeName)) {
1431
+ if (!appStateAdapter?.switchOverlay) {
1432
+ throw new Error("Overlay switching is not supported by the app state adapter");
1433
+ }
1434
+ await appStateAdapter.switchOverlay(routeName, params);
1435
+ return;
1107
1436
  }
1108
- await appStateAdapter.switchPage(page, params);
1437
+ throw new Error(`Route "${routeName}" not found`);
1109
1438
  } catch (error) {
1110
1439
  debugState.lastError = toErrorMessage(error);
1111
1440
  throw error;
@@ -1170,6 +1499,7 @@ async function startDebugSession(config = {}) {
1170
1499
  debugState.currentCursor = await executeScenarioCommand({
1171
1500
  subCommand: "getExecutionCursor"
1172
1501
  });
1502
+ debugState.currentStory = debugState.currentCursor?.story ?? null;
1173
1503
  } catch (error) {
1174
1504
  debugState.active = false;
1175
1505
  clearLocalRuntimeState();
@@ -1206,22 +1536,6 @@ async function stopDebugSession() {
1206
1536
  debugState.active = false;
1207
1537
  clearLocalRuntimeState();
1208
1538
  }
1209
- function clearDebugSessionRuntimeState() {
1210
- clearLocalRuntimeState();
1211
- void settleActiveFastForward(new Error("Debug session runtime state cleared"));
1212
- void settleActiveWarp(new Error("Debug session runtime state cleared"));
1213
- if (!debugState.active) {
1214
- return;
1215
- }
1216
- void enqueueDebugOperation(async () => {
1217
- try {
1218
- await clearRemoteCheckpoints();
1219
- } catch (error) {
1220
- debugState.lastError = toErrorMessage(error);
1221
- await currentConfig.onError(error);
1222
- }
1223
- });
1224
- }
1225
1539
  function useDebugSession() {
1226
1540
  const snapshot = useSnapshot2(debugState);
1227
1541
  return {
@@ -1230,10 +1544,83 @@ function useDebugSession() {
1230
1544
  stopDebugSession,
1231
1545
  restoreCheckpoint: debugSessionController.restoreCheckpoint,
1232
1546
  warp: debugSessionController.warp,
1233
- switchPage: debugSessionController.switchPage
1547
+ switchRoute: debugSessionController.switchRoute
1234
1548
  };
1235
1549
  }
1236
1550
 
1551
+ // src/ui.ts
1552
+ import { proxy as proxy3, useSnapshot as useSnapshot3 } from "valtio";
1553
+ var UI_DATA_ASSET_PATH = "assets:///data/ui.json";
1554
+ var activeUiSchema = null;
1555
+ var uiDataState = proxy3({});
1556
+ var uiRuntimeState = proxy3({
1557
+ initialized: false,
1558
+ error: null
1559
+ });
1560
+ function applyUiData(nextData) {
1561
+ for (const key of Object.keys(uiDataState)) {
1562
+ delete uiDataState[key];
1563
+ }
1564
+ Object.assign(uiDataState, nextData);
1565
+ uiRuntimeState.initialized = true;
1566
+ uiRuntimeState.error = null;
1567
+ }
1568
+ async function initUiData(schema) {
1569
+ uiRuntimeState.initialized = false;
1570
+ uiRuntimeState.error = null;
1571
+ activeUiSchema = schema;
1572
+ let rawData;
1573
+ try {
1574
+ const rawText = await Promise.resolve(
1575
+ executePluginCommand("system", {
1576
+ subCommand: "readFile",
1577
+ path: UI_DATA_ASSET_PATH
1578
+ })
1579
+ );
1580
+ rawData = JSON.parse(rawText);
1581
+ } catch (error) {
1582
+ const message = error instanceof Error ? error.message : String(error);
1583
+ uiRuntimeState.error = message;
1584
+ throw new Error(`Failed to initialize UI data: ${message}`);
1585
+ }
1586
+ const parsed = schema.safeParse(rawData);
1587
+ if (!parsed.success) {
1588
+ uiRuntimeState.error = parsed.error.message;
1589
+ throw new Error(`Invalid UI data: ${parsed.error.message}`);
1590
+ }
1591
+ applyUiData(parsed.data);
1592
+ }
1593
+ function replaceUiData(rawData) {
1594
+ if (!activeUiSchema) {
1595
+ throw new Error("UI data schema has not been initialized. Call initUiData() first.");
1596
+ }
1597
+ const parsed = activeUiSchema.safeParse(rawData);
1598
+ if (!parsed.success) {
1599
+ uiRuntimeState.error = parsed.error.message;
1600
+ throw new Error(`Invalid replacement UI data: ${parsed.error.message}`);
1601
+ }
1602
+ applyUiData(parsed.data);
1603
+ }
1604
+ function getUiData(name) {
1605
+ if (!uiRuntimeState.initialized) {
1606
+ throw new Error("UI data has not been initialized. Call initUiData() before reading it.");
1607
+ }
1608
+ if (!(name in uiDataState)) {
1609
+ throw new Error(`UI data "${name}" is not available.`);
1610
+ }
1611
+ return uiDataState[name];
1612
+ }
1613
+ function useUiData(name) {
1614
+ if (!uiRuntimeState.initialized) {
1615
+ throw new Error("UI data has not been initialized. Call initUiData() before rendering UI pages.");
1616
+ }
1617
+ const snapshot = useSnapshot3(uiDataState);
1618
+ if (!(name in snapshot)) {
1619
+ throw new Error(`UI data "${name}" is not available.`);
1620
+ }
1621
+ return snapshot[name];
1622
+ }
1623
+
1237
1624
  // src/debug/runtime.ts
1238
1625
  var currentConnection = null;
1239
1626
  var currentController = null;
@@ -1242,11 +1629,14 @@ function getRuntimeWebSocket() {
1242
1629
  return globalThis.WebSocket ?? null;
1243
1630
  }
1244
1631
  function isJumpRequestMessage(message) {
1245
- return message.type === "jump:request" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.markerId === "string" && (message.boundary === void 0 || message.boundary === "before" || message.boundary === "after") && (message.strategy === void 0 || message.strategy === "fast-forward" || message.strategy === "warp");
1632
+ return message.type === "jump:request" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.markerId === "string" && (message.story === void 0 || typeof message.story === "string") && (message.boundary === void 0 || message.boundary === "before" || message.boundary === "after") && (message.strategy === void 0 || message.strategy === "fast-forward" || message.strategy === "warp");
1246
1633
  }
1247
1634
  function isRouteRequestMessage(message) {
1248
1635
  return message.type === "route:request" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.page === "string" && (message.params === void 0 || typeof message.params === "object" && message.params !== null && !Array.isArray(message.params));
1249
1636
  }
1637
+ function isUiReplaceMessage(message) {
1638
+ return message.type === "ui:replace" && typeof message.sessionId === "string";
1639
+ }
1250
1640
  function sendRuntimeDebugMessage(socket, message) {
1251
1641
  const RuntimeWebSocket = getRuntimeWebSocket();
1252
1642
  if (!RuntimeWebSocket || socket.readyState !== RuntimeWebSocket.OPEN) {
@@ -1267,18 +1657,37 @@ function flushBufferedRequestMessages(socket, sessionId) {
1267
1657
  handleRuntimeDebugRequest(socket, sessionId, message);
1268
1658
  }
1269
1659
  }
1660
+ async function syncRuntimeDebugVariablesSnapshotSafe(reason) {
1661
+ try {
1662
+ await syncRuntimeDebugVariablesSnapshot(reason);
1663
+ } catch (error) {
1664
+ console.error("[debug] failed to sync runtime variables snapshot:", error);
1665
+ }
1666
+ }
1270
1667
  function handleRuntimeDebugRequest(socket, sessionId, message) {
1668
+ if (isUiReplaceMessage(message)) {
1669
+ try {
1670
+ replaceUiData(message.data);
1671
+ } catch (error) {
1672
+ console.error("[debug] failed to replace runtime UI data:", error);
1673
+ }
1674
+ return;
1675
+ }
1271
1676
  const controller = currentController;
1272
1677
  if (!controller) {
1273
1678
  bufferRequestMessage(message);
1274
1679
  return;
1275
1680
  }
1276
1681
  if (isJumpRequestMessage(message)) {
1682
+ setRuntimeDebugVariableEmissionSuspended(true);
1277
1683
  const runJump = message.strategy === "warp" ? controller.warp({
1278
1684
  markerId: message.markerId,
1685
+ story: message.story,
1279
1686
  boundary: message.boundary
1280
- }).then(() => true) : controller.restoreCheckpoint(message.markerId);
1281
- void runJump.then((restored) => {
1687
+ }).then(() => true) : controller.restoreCheckpoint(message.markerId, {
1688
+ story: message.story
1689
+ });
1690
+ void runJump.then(async (restored) => {
1282
1691
  if (!restored) {
1283
1692
  sendRuntimeDebugMessage(socket, {
1284
1693
  type: "jump:error",
@@ -1287,6 +1696,7 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1287
1696
  markerId: message.markerId,
1288
1697
  error: "Checkpoint not found"
1289
1698
  });
1699
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-error");
1290
1700
  return;
1291
1701
  }
1292
1702
  sendRuntimeDebugMessage(socket, {
@@ -1295,7 +1705,8 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1295
1705
  requestId: message.requestId,
1296
1706
  markerId: message.markerId
1297
1707
  });
1298
- }).catch((error) => {
1708
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-done");
1709
+ }).catch(async (error) => {
1299
1710
  sendRuntimeDebugMessage(socket, {
1300
1711
  type: "jump:error",
1301
1712
  sessionId,
@@ -1303,17 +1714,22 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1303
1714
  markerId: message.markerId,
1304
1715
  error: error instanceof Error ? error.message : String(error)
1305
1716
  });
1717
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-error");
1718
+ }).finally(() => {
1719
+ setRuntimeDebugVariableEmissionSuspended(false);
1306
1720
  });
1307
1721
  return;
1308
1722
  }
1309
- void controller.switchPage(message.page, message.params).then(() => {
1723
+ setRuntimeDebugVariableEmissionSuspended(true);
1724
+ void controller.switchRoute(message.page, message.params).then(async () => {
1310
1725
  sendRuntimeDebugMessage(socket, {
1311
1726
  type: "route:done",
1312
1727
  sessionId,
1313
1728
  requestId: message.requestId,
1314
1729
  page: message.page
1315
1730
  });
1316
- }).catch((error) => {
1731
+ await syncRuntimeDebugVariablesSnapshotSafe("route-done");
1732
+ }).catch(async (error) => {
1317
1733
  sendRuntimeDebugMessage(socket, {
1318
1734
  type: "route:error",
1319
1735
  sessionId,
@@ -1321,6 +1737,9 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1321
1737
  page: message.page,
1322
1738
  error: error instanceof Error ? error.message : String(error)
1323
1739
  });
1740
+ await syncRuntimeDebugVariablesSnapshotSafe("route-error");
1741
+ }).finally(() => {
1742
+ setRuntimeDebugVariableEmissionSuspended(false);
1324
1743
  });
1325
1744
  }
1326
1745
  async function readDebugParams() {
@@ -1345,6 +1764,7 @@ async function stopRuntimeDebugSession() {
1345
1764
  const connection = currentConnection;
1346
1765
  currentConnection = null;
1347
1766
  currentController = null;
1767
+ resetRuntimeDebugVariableMonitor();
1348
1768
  clearBufferedRequestMessages();
1349
1769
  const RuntimeWebSocket = getRuntimeWebSocket();
1350
1770
  await stopDebugSession();
@@ -1391,6 +1811,13 @@ async function startRuntimeDebugSession() {
1391
1811
  }
1392
1812
  }).then((controller) => {
1393
1813
  currentController = controller;
1814
+ setRuntimeDebugVariableMessageSender((message) => {
1815
+ sendRuntimeDebugMessage(socket, {
1816
+ ...message,
1817
+ sessionId
1818
+ });
1819
+ });
1820
+ void syncRuntimeDebugVariablesSnapshotSafe("init");
1394
1821
  flushBufferedRequestMessages(socket, sessionId);
1395
1822
  }).catch((error) => {
1396
1823
  console.error("[debug] failed to start runtime debug session:", error);
@@ -1403,6 +1830,7 @@ async function startRuntimeDebugSession() {
1403
1830
  }
1404
1831
  currentConnection = null;
1405
1832
  currentController = null;
1833
+ resetRuntimeDebugVariableMonitor();
1406
1834
  clearBufferedRequestMessages();
1407
1835
  void stopDebugSession();
1408
1836
  };
@@ -1429,6 +1857,10 @@ async function startRuntimeDebugSession() {
1429
1857
  handleRuntimeDebugRequest(socket, sessionId, message);
1430
1858
  return;
1431
1859
  }
1860
+ if (isUiReplaceMessage(message)) {
1861
+ handleRuntimeDebugRequest(socket, sessionId, message);
1862
+ return;
1863
+ }
1432
1864
  if (message.type && message.type !== "session:init") {
1433
1865
  console.debug("[debug] runtime received message:", message);
1434
1866
  }
@@ -1540,13 +1972,13 @@ async function terminateCurrentScenario() {
1540
1972
  await executePluginCommand("scenario", {
1541
1973
  subCommand: "terminateStory"
1542
1974
  });
1543
- clearDebugSessionRuntimeState();
1975
+ await clearLocalDebugSessionRuntimeState();
1544
1976
  } catch (error) {
1545
1977
  console.error("Failed to terminate scenario:", error);
1546
1978
  }
1547
1979
  }
1548
1980
  async function startScenarioSession(session, config) {
1549
- clearDebugSessionRuntimeState();
1981
+ await clearLocalDebugSessionRuntimeState();
1550
1982
  for (const story of config.stories) {
1551
1983
  if (!isCurrentScenarioSession(session)) return;
1552
1984
  try {
@@ -1662,49 +2094,31 @@ function useScenario(stories, startName, entryName, goNextOnLoad = false) {
1662
2094
 
1663
2095
  // src/hooks/useSoundEffect.ts
1664
2096
  import { useCallback, useEffect as useEffect2 } from "react";
1665
- var LOADED_SOUNDS = /* @__PURE__ */ new Set();
1666
2097
  function useSoundEffect(src) {
1667
2098
  useEffect2(() => {
1668
- const loadSound = async () => {
1669
- try {
1670
- await executePluginCommand("audio", {
1671
- subCommand: "load",
1672
- name: src,
1673
- src
1674
- });
1675
- } catch (error) {
1676
- console.error(`Failed to load sound effect ${src}:`, error);
1677
- }
1678
- LOADED_SOUNDS.add(src);
1679
- };
1680
- let needRelease = false;
1681
- if (!LOADED_SOUNDS.has(src)) {
1682
- void loadSound();
1683
- needRelease = true;
1684
- }
1685
2099
  return () => {
1686
- const releaseSound = () => {
1687
- try {
2100
+ try {
2101
+ if (src) {
1688
2102
  executePluginCommand("audio", {
1689
2103
  subCommand: "release",
1690
- name: src
2104
+ name: src,
2105
+ silentFail: true
1691
2106
  });
1692
- } catch (error) {
1693
- console.error(`Failed to release sound effect ${src}:`, error);
1694
2107
  }
1695
- LOADED_SOUNDS.delete(src);
1696
- };
1697
- if (needRelease) {
1698
- releaseSound();
2108
+ } catch (error) {
2109
+ console.error(`Failed to release sound effect ${src}:`, error);
1699
2110
  }
1700
2111
  };
1701
2112
  }, [src]);
1702
2113
  return useCallback(() => {
1703
2114
  try {
1704
- executePluginCommand("audio", {
1705
- subCommand: "play",
1706
- name: src
1707
- });
2115
+ if (src) {
2116
+ executePluginCommand("audio", {
2117
+ subCommand: "loadAndPlay",
2118
+ name: src,
2119
+ src
2120
+ });
2121
+ }
1708
2122
  } catch (error) {
1709
2123
  console.error(`Failed to play sound effect ${src}:`, error);
1710
2124
  }
@@ -1713,25 +2127,25 @@ function useSoundEffect(src) {
1713
2127
 
1714
2128
  // src/hooks/useStage.ts
1715
2129
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, createElement, useCallback as useCallback2 } from "react";
1716
- import { proxy as proxy3, useSnapshot as useSnapshot3 } from "valtio";
2130
+ import { proxy as proxy4, useSnapshot as useSnapshot4 } from "valtio";
1717
2131
  var SKIP_INTERVAL_MS = 80;
1718
2132
  var DEFAULT_FAST_FORWARD_STEP_LIMIT = 1e4;
1719
2133
  var DEFAULT_FAST_FORWARD_TIME_LIMIT_MS = 3e3;
1720
2134
  var autoRuntime = {
1721
2135
  defaultTailMs: 0
1722
2136
  };
1723
- var skipState = proxy3({ active: false });
1724
- var autoState = proxy3({ active: false });
1725
- var fastForwardState = proxy3({ active: false });
1726
- var warpState = proxy3({ active: false });
2137
+ var skipState = proxy4({ active: false });
2138
+ var autoState = proxy4({ active: false });
2139
+ var fastForwardState = proxy4({ active: false });
2140
+ var warpState = proxy4({ active: false });
1727
2141
  function useIsSkipping() {
1728
- return useSnapshot3(skipState).active;
2142
+ return useSnapshot4(skipState).active;
1729
2143
  }
1730
2144
  function useIsAutoing() {
1731
- return useSnapshot3(autoState).active;
2145
+ return useSnapshot4(autoState).active;
1732
2146
  }
1733
2147
  function useIsSeeking() {
1734
- return useSnapshot3(fastForwardState).active;
2148
+ return useSnapshot4(fastForwardState).active;
1735
2149
  }
1736
2150
  function getSeekingType() {
1737
2151
  if (warpState.active) return "warp";
@@ -2437,6 +2851,7 @@ function useBeforeHandleCommandCallback(callback) {
2437
2851
  // src/lib.ts
2438
2852
  export * from "@react-spring/core";
2439
2853
  export {
2854
+ DEBUG_EMPTY_PAGE,
2440
2855
  OverlayParamsContext,
2441
2856
  PageParamsContext,
2442
2857
  StageContextProvider,
@@ -2449,6 +2864,7 @@ export {
2449
2864
  createStackNavigator,
2450
2865
  createStage,
2451
2866
  createStaticNavigation,
2867
+ ensureArchiveVariableDefaults,
2452
2868
  executeNodeCommand,
2453
2869
  executePluginCommand,
2454
2870
  fastForwardState,
@@ -2456,11 +2872,16 @@ export {
2456
2872
  getNavigator,
2457
2873
  getSeekingType,
2458
2874
  getStageSize,
2875
+ getUiData,
2459
2876
  globalEventListeners,
2460
2877
  globalRequestAnimationFrameListeners,
2878
+ initGlobalVariableDefaults,
2879
+ initUiData,
2880
+ loadVariablesDocument,
2461
2881
  moyu_exports as moyu,
2462
2882
  nextLine,
2463
2883
  registerAppStateAdapter,
2884
+ replaceUiData,
2464
2885
  setDefaultAutoTailMs,
2465
2886
  setWaiting,
2466
2887
  skipState,
@@ -2487,5 +2908,6 @@ export {
2487
2908
  useSkipCallback,
2488
2909
  useSoundEffect,
2489
2910
  useStageContext,
2911
+ useUiData,
2490
2912
  warpState
2491
2913
  };