@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.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/lib.ts
32
32
  var lib_exports = {};
33
33
  __export(lib_exports, {
34
+ DEBUG_EMPTY_PAGE: () => DEBUG_EMPTY_PAGE,
34
35
  OverlayParamsContext: () => OverlayParamsContext,
35
36
  PageParamsContext: () => PageParamsContext,
36
37
  StageContextProvider: () => StageContextProvider,
@@ -43,6 +44,7 @@ __export(lib_exports, {
43
44
  createStackNavigator: () => createStackNavigator,
44
45
  createStage: () => createStage,
45
46
  createStaticNavigation: () => createStaticNavigation,
47
+ ensureArchiveVariableDefaults: () => ensureArchiveVariableDefaults,
46
48
  executeNodeCommand: () => executeNodeCommand,
47
49
  executePluginCommand: () => executePluginCommand,
48
50
  fastForwardState: () => fastForwardState,
@@ -50,11 +52,16 @@ __export(lib_exports, {
50
52
  getNavigator: () => getNavigator,
51
53
  getSeekingType: () => getSeekingType,
52
54
  getStageSize: () => getStageSize,
55
+ getUiData: () => getUiData,
53
56
  globalEventListeners: () => globalEventListeners,
54
57
  globalRequestAnimationFrameListeners: () => globalRequestAnimationFrameListeners,
58
+ initGlobalVariableDefaults: () => initGlobalVariableDefaults,
59
+ initUiData: () => initUiData,
60
+ loadVariablesDocument: () => loadVariablesDocument,
55
61
  moyu: () => moyu_exports,
56
62
  nextLine: () => nextLine,
57
63
  registerAppStateAdapter: () => registerAppStateAdapter,
64
+ replaceUiData: () => replaceUiData,
58
65
  setDefaultAutoTailMs: () => setDefaultAutoTailMs,
59
66
  setWaiting: () => setWaiting,
60
67
  skipState: () => skipState,
@@ -81,6 +88,7 @@ __export(lib_exports, {
81
88
  useSkipCallback: () => useSkipCallback,
82
89
  useSoundEffect: () => useSoundEffect,
83
90
  useStageContext: () => useStageContext,
91
+ useUiData: () => useUiData,
84
92
  warpState: () => warpState
85
93
  });
86
94
  module.exports = __toCommonJS(lib_exports);
@@ -406,6 +414,259 @@ function createRoot(options) {
406
414
  };
407
415
  }
408
416
 
417
+ // src/variables.ts
418
+ var VARIABLES_DATA_ASSET_PATH = "assets:///data/variables.json";
419
+ function normalizeVariableDefinitions(value) {
420
+ if (!Array.isArray(value)) {
421
+ return [];
422
+ }
423
+ return value.flatMap((entry) => {
424
+ if (!entry || typeof entry !== "object") {
425
+ return [];
426
+ }
427
+ const definition = entry;
428
+ if (typeof definition.id !== "string" || definition.id.length === 0) {
429
+ return [];
430
+ }
431
+ return [{ id: definition.id, defaultValue: definition.defaultValue }];
432
+ });
433
+ }
434
+ async function loadVariablesDocument() {
435
+ let rawText;
436
+ try {
437
+ rawText = await executePluginCommand("system", {
438
+ subCommand: "readFile",
439
+ path: VARIABLES_DATA_ASSET_PATH
440
+ });
441
+ } catch {
442
+ return null;
443
+ }
444
+ let rawData;
445
+ try {
446
+ rawData = JSON.parse(rawText);
447
+ } catch (error) {
448
+ console.error("Failed to parse variable defaults data:", error);
449
+ return null;
450
+ }
451
+ if (!rawData || typeof rawData !== "object") {
452
+ return {
453
+ archive: [],
454
+ global: []
455
+ };
456
+ }
457
+ const document = rawData;
458
+ return {
459
+ archive: normalizeVariableDefinitions(document.archive),
460
+ global: normalizeVariableDefinitions(document.global)
461
+ };
462
+ }
463
+ function toVariableRecord(value) {
464
+ if (value instanceof Map) {
465
+ return Object.fromEntries(value);
466
+ }
467
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
468
+ return {};
469
+ }
470
+ return value;
471
+ }
472
+ async function ensureVariableDefaults(scope) {
473
+ const document = await loadVariablesDocument();
474
+ if (!document) {
475
+ return;
476
+ }
477
+ const definitions = document[scope];
478
+ if (definitions.length === 0) {
479
+ return;
480
+ }
481
+ const currentVariables = toVariableRecord(
482
+ await executePluginCommand(
483
+ "scenario",
484
+ scope === "global" ? { subCommand: "getPermanentVariables" } : { subCommand: "getVariables" }
485
+ )
486
+ );
487
+ const missingVariables = {};
488
+ for (const definition of definitions) {
489
+ if (!Object.hasOwn(currentVariables, definition.id)) {
490
+ missingVariables[definition.id] = definition.defaultValue;
491
+ }
492
+ }
493
+ if (Object.keys(missingVariables).length === 0) {
494
+ return;
495
+ }
496
+ await executePluginCommand(
497
+ "scenario",
498
+ scope === "global" ? {
499
+ subCommand: "setPermanentVariables",
500
+ variables: missingVariables
501
+ } : {
502
+ subCommand: "setVariables",
503
+ variables: missingVariables
504
+ }
505
+ );
506
+ reportRuntimeDebugVariablesSet(scope, missingVariables);
507
+ }
508
+ async function initGlobalVariableDefaults() {
509
+ await ensureVariableDefaults("global");
510
+ }
511
+ async function ensureArchiveVariableDefaults() {
512
+ await ensureVariableDefaults("archive");
513
+ }
514
+
515
+ // src/debug/variableMonitor.ts
516
+ var sendRuntimeDebugVariableMessage = null;
517
+ var allowlistCache = null;
518
+ var allowlistPromise = null;
519
+ var variableEmissionSuspended = false;
520
+ function createEmptyAllowlist() {
521
+ return {
522
+ archive: /* @__PURE__ */ new Set(),
523
+ global: /* @__PURE__ */ new Set()
524
+ };
525
+ }
526
+ function isJsonValue(value) {
527
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
528
+ return true;
529
+ }
530
+ if (Array.isArray(value)) {
531
+ return value.every((item) => isJsonValue(item));
532
+ }
533
+ if (!value || typeof value !== "object") {
534
+ return false;
535
+ }
536
+ return Object.values(value).every((item) => isJsonValue(item));
537
+ }
538
+ async function loadRuntimeDebugVariableAllowlist() {
539
+ const document = await loadVariablesDocument();
540
+ if (!document) {
541
+ return createEmptyAllowlist();
542
+ }
543
+ return {
544
+ archive: new Set(document.archive.map((def) => def.id)),
545
+ global: new Set(document.global.map((def) => def.id))
546
+ };
547
+ }
548
+ function toJsonValueRecord(value) {
549
+ if (value instanceof Map) {
550
+ return Object.fromEntries(
551
+ Array.from(value.entries()).flatMap(([key, entryValue]) => {
552
+ if (typeof key !== "string" || !isJsonValue(entryValue)) {
553
+ return [];
554
+ }
555
+ return [[key, entryValue]];
556
+ })
557
+ );
558
+ }
559
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
560
+ return {};
561
+ }
562
+ return Object.fromEntries(
563
+ Object.entries(value).flatMap(([key, entryValue]) => {
564
+ if (!isJsonValue(entryValue)) {
565
+ return [];
566
+ }
567
+ return [[key, entryValue]];
568
+ })
569
+ );
570
+ }
571
+ function filterVariablesByAllowlist(scope, values, allowlist) {
572
+ const allowedIds = allowlist[scope];
573
+ if (allowedIds.size === 0) {
574
+ return {};
575
+ }
576
+ return Object.fromEntries(
577
+ Object.entries(values).flatMap(([key, value]) => allowedIds.has(key) ? [[key, value]] : [])
578
+ );
579
+ }
580
+ function hasRecordEntries(value) {
581
+ return !!value && Object.keys(value).length > 0;
582
+ }
583
+ async function ensureRuntimeDebugVariableAllowlist() {
584
+ if (allowlistCache) {
585
+ return allowlistCache;
586
+ }
587
+ if (!allowlistPromise) {
588
+ allowlistPromise = loadRuntimeDebugVariableAllowlist().then((allowlist) => {
589
+ allowlistCache = allowlist;
590
+ return allowlist;
591
+ }).finally(() => {
592
+ allowlistPromise = null;
593
+ });
594
+ }
595
+ return allowlistPromise;
596
+ }
597
+ async function emitRuntimeDebugVariablesChanged(scope, variables) {
598
+ const sender = sendRuntimeDebugVariableMessage;
599
+ if (!sender || variableEmissionSuspended) {
600
+ return;
601
+ }
602
+ const allowlist = await ensureRuntimeDebugVariableAllowlist();
603
+ if (!sendRuntimeDebugVariableMessage || sendRuntimeDebugVariableMessage !== sender || variableEmissionSuspended) {
604
+ return;
605
+ }
606
+ const filtered = filterVariablesByAllowlist(scope, variables, allowlist);
607
+ if (!hasRecordEntries(filtered)) {
608
+ return;
609
+ }
610
+ sender({
611
+ type: "variables:changed",
612
+ archiveSet: scope === "archive" ? filtered : void 0,
613
+ globalSet: scope === "global" ? filtered : void 0
614
+ });
615
+ }
616
+ function setRuntimeDebugVariableMessageSender(sender) {
617
+ sendRuntimeDebugVariableMessage = sender;
618
+ if (sender) {
619
+ void ensureRuntimeDebugVariableAllowlist();
620
+ }
621
+ }
622
+ function setRuntimeDebugVariableEmissionSuspended(suspended) {
623
+ variableEmissionSuspended = suspended;
624
+ }
625
+ function resetRuntimeDebugVariableMonitor() {
626
+ sendRuntimeDebugVariableMessage = null;
627
+ allowlistCache = null;
628
+ allowlistPromise = null;
629
+ variableEmissionSuspended = false;
630
+ }
631
+ function reportRuntimeDebugVariableSet(scope, key, value) {
632
+ if (key.length === 0) {
633
+ return;
634
+ }
635
+ void emitRuntimeDebugVariablesChanged(scope, {
636
+ [key]: value
637
+ });
638
+ }
639
+ function reportRuntimeDebugVariablesSet(scope, variables) {
640
+ if (Object.keys(variables).length === 0) {
641
+ return;
642
+ }
643
+ void emitRuntimeDebugVariablesChanged(scope, variables);
644
+ }
645
+ async function syncRuntimeDebugVariablesSnapshot(reason) {
646
+ const sender = sendRuntimeDebugVariableMessage;
647
+ if (!sender) {
648
+ return;
649
+ }
650
+ const [allowlist, archiveVariables2, globalVariables2] = await Promise.all([
651
+ ensureRuntimeDebugVariableAllowlist(),
652
+ executePluginCommand("scenario", {
653
+ subCommand: "getVariables"
654
+ }),
655
+ executePluginCommand("scenario", {
656
+ subCommand: "getPermanentVariables"
657
+ })
658
+ ]);
659
+ if (!sendRuntimeDebugVariableMessage || sendRuntimeDebugVariableMessage !== sender) {
660
+ return;
661
+ }
662
+ sender({
663
+ type: "variables:snapshot",
664
+ reason,
665
+ archive: filterVariablesByAllowlist("archive", toJsonValueRecord(archiveVariables2), allowlist),
666
+ global: filterVariablesByAllowlist("global", toJsonValueRecord(globalVariables2), allowlist)
667
+ });
668
+ }
669
+
409
670
  // src/sandbox.ts
410
671
  var JS_GLOBAL_KEYS = new Set(Object.getOwnPropertyNames(globalThis));
411
672
  var JS_GLOBAL_KEYS_EXCLUDED = /* @__PURE__ */ new Set(["window", "globalThis", "LOCAL", "ARCHIVE", "GLOBAL"]);
@@ -456,6 +717,7 @@ var archiveVariables = new Proxy(
456
717
  name: key,
457
718
  value
458
719
  });
720
+ reportRuntimeDebugVariableSet("archive", key, value);
459
721
  return true;
460
722
  }
461
723
  return false;
@@ -481,6 +743,7 @@ var globalVariables = new Proxy(
481
743
  key,
482
744
  value
483
745
  });
746
+ reportRuntimeDebugVariableSet("global", key, value);
484
747
  return true;
485
748
  }
486
749
  return false;
@@ -488,7 +751,7 @@ var globalVariables = new Proxy(
488
751
  }
489
752
  );
490
753
  function safeEval(code, sandbox) {
491
- const proxy4 = new Proxy(sandbox, {
754
+ const proxy5 = new Proxy(sandbox, {
492
755
  has(target, key) {
493
756
  if (typeof key === "string" && JS_GLOBAL_KEYS.has(key)) {
494
757
  return false;
@@ -496,7 +759,7 @@ function safeEval(code, sandbox) {
496
759
  return key in target || key === "LOCAL" || key === "ARCHIVE" || key === "GLOBAL" || key === "window" || key === "globalThis";
497
760
  },
498
761
  get(target, key) {
499
- if (key === "window" || key === "globalThis") return proxy4;
762
+ if (key === "window" || key === "globalThis") return proxy5;
500
763
  if (key === "LOCAL") return localVariables;
501
764
  if (key === "ARCHIVE") return archiveVariables;
502
765
  if (key === "GLOBAL") return globalVariables;
@@ -508,7 +771,7 @@ function safeEval(code, sandbox) {
508
771
  return true;
509
772
  }
510
773
  });
511
- return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy4)(proxy4);
774
+ return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy5)(proxy5);
512
775
  }
513
776
  globalThis.__moyu_eval_sandbox = (code) => {
514
777
  try {
@@ -695,6 +958,7 @@ var import_valtio = require("valtio");
695
958
  var import_jsx_runtime = require("react/jsx-runtime");
696
959
  var PageParamsContext = (0, import_react2.createContext)(void 0);
697
960
  var OverlayParamsContext = (0, import_react2.createContext)(void 0);
961
+ var DEBUG_EMPTY_PAGE = "debug-empty";
698
962
  var overlayIdCounter = 0;
699
963
  var generateOverlayId = () => {
700
964
  return `overlay-${++overlayIdCounter}-${Date.now()}`;
@@ -722,6 +986,12 @@ function createStackNavigator(options) {
722
986
  for (const [key, config] of Object.entries(options.pages)) {
723
987
  normalizedPages[key] = normalizeDescriptor(config);
724
988
  }
989
+ if (normalizedPages[DEBUG_EMPTY_PAGE]) {
990
+ throw new Error(`Navigation Error: Page name "${DEBUG_EMPTY_PAGE}" is reserved.`);
991
+ }
992
+ normalizedPages[DEBUG_EMPTY_PAGE] = {
993
+ component: () => null
994
+ };
725
995
  const normalizedOverlays = {};
726
996
  if (options.overlays) {
727
997
  for (const [key, config] of Object.entries(options.overlays)) {
@@ -751,6 +1021,9 @@ function createStackNavigator(options) {
751
1021
  hasPage: (page) => {
752
1022
  return normalizedPages[page] !== void 0;
753
1023
  },
1024
+ hasOverlay: (overlay) => {
1025
+ return normalizedOverlays[overlay] !== void 0;
1026
+ },
754
1027
  goBack: () => {
755
1028
  console.warn("goBack() not implemented yet");
756
1029
  },
@@ -838,6 +1111,7 @@ var debugState = (0, import_valtio2.proxy)({
838
1111
  active: false,
839
1112
  checkpoints: {},
840
1113
  currentCursor: null,
1114
+ currentStory: null,
841
1115
  restoring: false,
842
1116
  lastError: null
843
1117
  });
@@ -881,9 +1155,37 @@ function reportDebugSessionError(error) {
881
1155
  function clearLocalRuntimeState() {
882
1156
  debugState.checkpoints = {};
883
1157
  debugState.currentCursor = null;
1158
+ debugState.currentStory = null;
884
1159
  debugState.restoring = false;
885
1160
  debugState.lastError = null;
886
1161
  }
1162
+ async function restartJumpStoryFromHead(targetStory, currentStory) {
1163
+ debugState.checkpoints = {};
1164
+ debugState.currentCursor = null;
1165
+ debugState.currentStory = null;
1166
+ if (targetStory) {
1167
+ if (appStateAdapter?.restartStoryFromHead) {
1168
+ await appStateAdapter.restartStoryFromHead({
1169
+ story: targetStory,
1170
+ entry: "entry"
1171
+ });
1172
+ } else if (currentStory === targetStory && appStateAdapter?.restartCurrentStoryFromHead) {
1173
+ await appStateAdapter.restartCurrentStoryFromHead();
1174
+ } else {
1175
+ throw new Error("Restarting the target story from head is not supported by the app state adapter");
1176
+ }
1177
+ debugState.currentCursor = await executeScenarioCommand({
1178
+ subCommand: "getExecutionCursor"
1179
+ });
1180
+ debugState.currentStory = debugState.currentCursor?.story ?? targetStory;
1181
+ return;
1182
+ }
1183
+ if (!appStateAdapter?.restartCurrentStoryFromHead) {
1184
+ throw new Error("Restarting the current story from head is not supported by the app state adapter");
1185
+ }
1186
+ await appStateAdapter.restartCurrentStoryFromHead();
1187
+ debugState.currentCursor = null;
1188
+ }
887
1189
  async function doSettleActiveSeek(op, clearSlot, error) {
888
1190
  if (!op) {
889
1191
  return;
@@ -912,6 +1214,11 @@ async function settleActiveWarp(error) {
912
1214
  activeWarp = null;
913
1215
  }, error);
914
1216
  }
1217
+ async function clearLocalDebugSessionRuntimeState() {
1218
+ await settleActiveFastForward(new Error("Debug session runtime state cleared"));
1219
+ await settleActiveWarp(new Error("Debug session runtime state cleared"));
1220
+ clearLocalRuntimeState();
1221
+ }
915
1222
  async function clearRemoteCheckpoints() {
916
1223
  await executeScenarioCommand({ subCommand: "clearCheckpoints" });
917
1224
  }
@@ -936,6 +1243,7 @@ async function syncLocalWarpCheckpoint(targetMarkerId) {
936
1243
  appState: appStateAdapter ? await appStateAdapter.capture() : void 0
937
1244
  };
938
1245
  debugState.currentCursor = checkpointCursor;
1246
+ debugState.currentStory = checkpointCursor.story;
939
1247
  debugState.checkpoints[targetMarkerId] = checkpoint;
940
1248
  await currentConfig.onMarkerEnter(checkpoint);
941
1249
  }
@@ -959,9 +1267,16 @@ function notifyMarkerEnter(checkpoint) {
959
1267
  reportDebugSessionError(error);
960
1268
  }
961
1269
  }
962
- async function prepareJumpStart(markerId) {
1270
+ async function prepareJumpStart(markerId, options) {
1271
+ const currentStory = debugState.currentStory ?? debugState.currentCursor?.story ?? null;
1272
+ const targetStory = options?.story;
1273
+ let restartedFromHead = false;
1274
+ if (targetStory && targetStory !== currentStory) {
1275
+ await restartJumpStoryFromHead(targetStory, currentStory);
1276
+ restartedFromHead = true;
1277
+ }
963
1278
  const checkpoint = debugState.checkpoints[markerId];
964
- if (checkpoint) {
1279
+ if (!restartedFromHead && checkpoint && (!targetStory || checkpoint.cursor.story === targetStory)) {
965
1280
  const restored2 = await executeScenarioCommand({
966
1281
  subCommand: "restoreCheckpoint",
967
1282
  key: markerId
@@ -973,6 +1288,7 @@ async function prepareJumpStart(markerId) {
973
1288
  await appStateAdapter.restore(checkpoint.appState);
974
1289
  }
975
1290
  debugState.currentCursor = checkpoint.cursor;
1291
+ debugState.currentStory = checkpoint.cursor.story;
976
1292
  return false;
977
1293
  }
978
1294
  const lookup = await executeScenarioCommand({
@@ -980,16 +1296,15 @@ async function prepareJumpStart(markerId) {
980
1296
  key: markerId
981
1297
  });
982
1298
  if (!lookup.markerExists) {
983
- throw new Error(`Marker ${markerId} is not available in the current story`);
1299
+ throw new Error(
1300
+ targetStory ? `Marker ${markerId} is not available in story ${targetStory}` : `Marker ${markerId} is not available in the current story`
1301
+ );
984
1302
  }
985
1303
  const startCheckpointKey = lookup.checkpointKey ?? null;
986
1304
  if (!startCheckpointKey) {
987
- if (!appStateAdapter?.restartCurrentStoryFromHead) {
988
- throw new Error("Restarting the current story from head is not supported by the app state adapter");
1305
+ if (!restartedFromHead) {
1306
+ await restartJumpStoryFromHead(targetStory, currentStory);
989
1307
  }
990
- await appStateAdapter.restartCurrentStoryFromHead();
991
- debugState.checkpoints = {};
992
- debugState.currentCursor = null;
993
1308
  return true;
994
1309
  }
995
1310
  const startCheckpoint = debugState.checkpoints[startCheckpointKey];
@@ -1007,6 +1322,7 @@ async function prepareJumpStart(markerId) {
1007
1322
  await appStateAdapter.restore(startCheckpoint.appState);
1008
1323
  }
1009
1324
  debugState.currentCursor = startCheckpoint.cursor;
1325
+ debugState.currentStory = startCheckpoint.cursor.story;
1010
1326
  return true;
1011
1327
  }
1012
1328
  function handleMarkerEnter(marker) {
@@ -1019,6 +1335,7 @@ function handleMarkerEnter(marker) {
1019
1335
  appState: captureCheckpointAppStateSync()
1020
1336
  };
1021
1337
  debugState.currentCursor = checkpoint.cursor;
1338
+ debugState.currentStory = checkpoint.cursor.story;
1022
1339
  debugState.checkpoints[marker.markerId] = checkpoint;
1023
1340
  notifyMarkerEnter(checkpoint);
1024
1341
  if (activeFastForward?.targetMarkerId === marker.markerId) {
@@ -1079,11 +1396,11 @@ async function runWarp(options) {
1079
1396
  await completion;
1080
1397
  }
1081
1398
  var debugSessionController = {
1082
- async restoreCheckpoint(markerId) {
1399
+ async restoreCheckpoint(markerId, options) {
1083
1400
  debugState.restoring = true;
1084
1401
  debugState.lastError = null;
1085
1402
  try {
1086
- const needsFastForward = await enqueueDebugOperation(async () => prepareJumpStart(markerId));
1403
+ const needsFastForward = await enqueueDebugOperation(async () => prepareJumpStart(markerId, options));
1087
1404
  if (needsFastForward) {
1088
1405
  await runFastForwardToMarker(markerId);
1089
1406
  }
@@ -1099,7 +1416,7 @@ var debugSessionController = {
1099
1416
  debugState.restoring = true;
1100
1417
  debugState.lastError = null;
1101
1418
  try {
1102
- const needsWarp = await enqueueDebugOperation(async () => prepareJumpStart(options.markerId));
1419
+ const needsWarp = await enqueueDebugOperation(async () => prepareJumpStart(options.markerId, options));
1103
1420
  if (needsWarp) {
1104
1421
  await runWarp(options);
1105
1422
  }
@@ -1110,14 +1427,26 @@ var debugSessionController = {
1110
1427
  debugState.restoring = false;
1111
1428
  }
1112
1429
  },
1113
- async switchPage(page, params) {
1430
+ async switchRoute(routeName, params) {
1114
1431
  return enqueueDebugOperation(async () => {
1115
1432
  debugState.lastError = null;
1116
1433
  try {
1117
- if (!appStateAdapter?.switchPage) {
1118
- throw new Error("Page switching is not supported by the app state adapter");
1434
+ const navigator = getNavigator();
1435
+ if (navigator.hasPage(routeName)) {
1436
+ if (!appStateAdapter?.switchPage) {
1437
+ throw new Error("Page switching is not supported by the app state adapter");
1438
+ }
1439
+ await appStateAdapter.switchPage(routeName, params);
1440
+ return;
1441
+ }
1442
+ if (navigator.hasOverlay(routeName)) {
1443
+ if (!appStateAdapter?.switchOverlay) {
1444
+ throw new Error("Overlay switching is not supported by the app state adapter");
1445
+ }
1446
+ await appStateAdapter.switchOverlay(routeName, params);
1447
+ return;
1119
1448
  }
1120
- await appStateAdapter.switchPage(page, params);
1449
+ throw new Error(`Route "${routeName}" not found`);
1121
1450
  } catch (error) {
1122
1451
  debugState.lastError = toErrorMessage(error);
1123
1452
  throw error;
@@ -1182,6 +1511,7 @@ async function startDebugSession(config = {}) {
1182
1511
  debugState.currentCursor = await executeScenarioCommand({
1183
1512
  subCommand: "getExecutionCursor"
1184
1513
  });
1514
+ debugState.currentStory = debugState.currentCursor?.story ?? null;
1185
1515
  } catch (error) {
1186
1516
  debugState.active = false;
1187
1517
  clearLocalRuntimeState();
@@ -1218,22 +1548,6 @@ async function stopDebugSession() {
1218
1548
  debugState.active = false;
1219
1549
  clearLocalRuntimeState();
1220
1550
  }
1221
- function clearDebugSessionRuntimeState() {
1222
- clearLocalRuntimeState();
1223
- void settleActiveFastForward(new Error("Debug session runtime state cleared"));
1224
- void settleActiveWarp(new Error("Debug session runtime state cleared"));
1225
- if (!debugState.active) {
1226
- return;
1227
- }
1228
- void enqueueDebugOperation(async () => {
1229
- try {
1230
- await clearRemoteCheckpoints();
1231
- } catch (error) {
1232
- debugState.lastError = toErrorMessage(error);
1233
- await currentConfig.onError(error);
1234
- }
1235
- });
1236
- }
1237
1551
  function useDebugSession() {
1238
1552
  const snapshot = (0, import_valtio2.useSnapshot)(debugState);
1239
1553
  return {
@@ -1242,10 +1556,83 @@ function useDebugSession() {
1242
1556
  stopDebugSession,
1243
1557
  restoreCheckpoint: debugSessionController.restoreCheckpoint,
1244
1558
  warp: debugSessionController.warp,
1245
- switchPage: debugSessionController.switchPage
1559
+ switchRoute: debugSessionController.switchRoute
1246
1560
  };
1247
1561
  }
1248
1562
 
1563
+ // src/ui.ts
1564
+ var import_valtio3 = require("valtio");
1565
+ var UI_DATA_ASSET_PATH = "assets:///data/ui.json";
1566
+ var activeUiSchema = null;
1567
+ var uiDataState = (0, import_valtio3.proxy)({});
1568
+ var uiRuntimeState = (0, import_valtio3.proxy)({
1569
+ initialized: false,
1570
+ error: null
1571
+ });
1572
+ function applyUiData(nextData) {
1573
+ for (const key of Object.keys(uiDataState)) {
1574
+ delete uiDataState[key];
1575
+ }
1576
+ Object.assign(uiDataState, nextData);
1577
+ uiRuntimeState.initialized = true;
1578
+ uiRuntimeState.error = null;
1579
+ }
1580
+ async function initUiData(schema) {
1581
+ uiRuntimeState.initialized = false;
1582
+ uiRuntimeState.error = null;
1583
+ activeUiSchema = schema;
1584
+ let rawData;
1585
+ try {
1586
+ const rawText = await Promise.resolve(
1587
+ executePluginCommand("system", {
1588
+ subCommand: "readFile",
1589
+ path: UI_DATA_ASSET_PATH
1590
+ })
1591
+ );
1592
+ rawData = JSON.parse(rawText);
1593
+ } catch (error) {
1594
+ const message = error instanceof Error ? error.message : String(error);
1595
+ uiRuntimeState.error = message;
1596
+ throw new Error(`Failed to initialize UI data: ${message}`);
1597
+ }
1598
+ const parsed = schema.safeParse(rawData);
1599
+ if (!parsed.success) {
1600
+ uiRuntimeState.error = parsed.error.message;
1601
+ throw new Error(`Invalid UI data: ${parsed.error.message}`);
1602
+ }
1603
+ applyUiData(parsed.data);
1604
+ }
1605
+ function replaceUiData(rawData) {
1606
+ if (!activeUiSchema) {
1607
+ throw new Error("UI data schema has not been initialized. Call initUiData() first.");
1608
+ }
1609
+ const parsed = activeUiSchema.safeParse(rawData);
1610
+ if (!parsed.success) {
1611
+ uiRuntimeState.error = parsed.error.message;
1612
+ throw new Error(`Invalid replacement UI data: ${parsed.error.message}`);
1613
+ }
1614
+ applyUiData(parsed.data);
1615
+ }
1616
+ function getUiData(name) {
1617
+ if (!uiRuntimeState.initialized) {
1618
+ throw new Error("UI data has not been initialized. Call initUiData() before reading it.");
1619
+ }
1620
+ if (!(name in uiDataState)) {
1621
+ throw new Error(`UI data "${name}" is not available.`);
1622
+ }
1623
+ return uiDataState[name];
1624
+ }
1625
+ function useUiData(name) {
1626
+ if (!uiRuntimeState.initialized) {
1627
+ throw new Error("UI data has not been initialized. Call initUiData() before rendering UI pages.");
1628
+ }
1629
+ const snapshot = (0, import_valtio3.useSnapshot)(uiDataState);
1630
+ if (!(name in snapshot)) {
1631
+ throw new Error(`UI data "${name}" is not available.`);
1632
+ }
1633
+ return snapshot[name];
1634
+ }
1635
+
1249
1636
  // src/debug/runtime.ts
1250
1637
  var currentConnection = null;
1251
1638
  var currentController = null;
@@ -1254,11 +1641,14 @@ function getRuntimeWebSocket() {
1254
1641
  return globalThis.WebSocket ?? null;
1255
1642
  }
1256
1643
  function isJumpRequestMessage(message) {
1257
- 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");
1644
+ 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");
1258
1645
  }
1259
1646
  function isRouteRequestMessage(message) {
1260
1647
  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));
1261
1648
  }
1649
+ function isUiReplaceMessage(message) {
1650
+ return message.type === "ui:replace" && typeof message.sessionId === "string";
1651
+ }
1262
1652
  function sendRuntimeDebugMessage(socket, message) {
1263
1653
  const RuntimeWebSocket = getRuntimeWebSocket();
1264
1654
  if (!RuntimeWebSocket || socket.readyState !== RuntimeWebSocket.OPEN) {
@@ -1279,18 +1669,37 @@ function flushBufferedRequestMessages(socket, sessionId) {
1279
1669
  handleRuntimeDebugRequest(socket, sessionId, message);
1280
1670
  }
1281
1671
  }
1672
+ async function syncRuntimeDebugVariablesSnapshotSafe(reason) {
1673
+ try {
1674
+ await syncRuntimeDebugVariablesSnapshot(reason);
1675
+ } catch (error) {
1676
+ console.error("[debug] failed to sync runtime variables snapshot:", error);
1677
+ }
1678
+ }
1282
1679
  function handleRuntimeDebugRequest(socket, sessionId, message) {
1680
+ if (isUiReplaceMessage(message)) {
1681
+ try {
1682
+ replaceUiData(message.data);
1683
+ } catch (error) {
1684
+ console.error("[debug] failed to replace runtime UI data:", error);
1685
+ }
1686
+ return;
1687
+ }
1283
1688
  const controller = currentController;
1284
1689
  if (!controller) {
1285
1690
  bufferRequestMessage(message);
1286
1691
  return;
1287
1692
  }
1288
1693
  if (isJumpRequestMessage(message)) {
1694
+ setRuntimeDebugVariableEmissionSuspended(true);
1289
1695
  const runJump = message.strategy === "warp" ? controller.warp({
1290
1696
  markerId: message.markerId,
1697
+ story: message.story,
1291
1698
  boundary: message.boundary
1292
- }).then(() => true) : controller.restoreCheckpoint(message.markerId);
1293
- void runJump.then((restored) => {
1699
+ }).then(() => true) : controller.restoreCheckpoint(message.markerId, {
1700
+ story: message.story
1701
+ });
1702
+ void runJump.then(async (restored) => {
1294
1703
  if (!restored) {
1295
1704
  sendRuntimeDebugMessage(socket, {
1296
1705
  type: "jump:error",
@@ -1299,6 +1708,7 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1299
1708
  markerId: message.markerId,
1300
1709
  error: "Checkpoint not found"
1301
1710
  });
1711
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-error");
1302
1712
  return;
1303
1713
  }
1304
1714
  sendRuntimeDebugMessage(socket, {
@@ -1307,7 +1717,8 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1307
1717
  requestId: message.requestId,
1308
1718
  markerId: message.markerId
1309
1719
  });
1310
- }).catch((error) => {
1720
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-done");
1721
+ }).catch(async (error) => {
1311
1722
  sendRuntimeDebugMessage(socket, {
1312
1723
  type: "jump:error",
1313
1724
  sessionId,
@@ -1315,17 +1726,22 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1315
1726
  markerId: message.markerId,
1316
1727
  error: error instanceof Error ? error.message : String(error)
1317
1728
  });
1729
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-error");
1730
+ }).finally(() => {
1731
+ setRuntimeDebugVariableEmissionSuspended(false);
1318
1732
  });
1319
1733
  return;
1320
1734
  }
1321
- void controller.switchPage(message.page, message.params).then(() => {
1735
+ setRuntimeDebugVariableEmissionSuspended(true);
1736
+ void controller.switchRoute(message.page, message.params).then(async () => {
1322
1737
  sendRuntimeDebugMessage(socket, {
1323
1738
  type: "route:done",
1324
1739
  sessionId,
1325
1740
  requestId: message.requestId,
1326
1741
  page: message.page
1327
1742
  });
1328
- }).catch((error) => {
1743
+ await syncRuntimeDebugVariablesSnapshotSafe("route-done");
1744
+ }).catch(async (error) => {
1329
1745
  sendRuntimeDebugMessage(socket, {
1330
1746
  type: "route:error",
1331
1747
  sessionId,
@@ -1333,6 +1749,9 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1333
1749
  page: message.page,
1334
1750
  error: error instanceof Error ? error.message : String(error)
1335
1751
  });
1752
+ await syncRuntimeDebugVariablesSnapshotSafe("route-error");
1753
+ }).finally(() => {
1754
+ setRuntimeDebugVariableEmissionSuspended(false);
1336
1755
  });
1337
1756
  }
1338
1757
  async function readDebugParams() {
@@ -1357,6 +1776,7 @@ async function stopRuntimeDebugSession() {
1357
1776
  const connection = currentConnection;
1358
1777
  currentConnection = null;
1359
1778
  currentController = null;
1779
+ resetRuntimeDebugVariableMonitor();
1360
1780
  clearBufferedRequestMessages();
1361
1781
  const RuntimeWebSocket = getRuntimeWebSocket();
1362
1782
  await stopDebugSession();
@@ -1403,6 +1823,13 @@ async function startRuntimeDebugSession() {
1403
1823
  }
1404
1824
  }).then((controller) => {
1405
1825
  currentController = controller;
1826
+ setRuntimeDebugVariableMessageSender((message) => {
1827
+ sendRuntimeDebugMessage(socket, {
1828
+ ...message,
1829
+ sessionId
1830
+ });
1831
+ });
1832
+ void syncRuntimeDebugVariablesSnapshotSafe("init");
1406
1833
  flushBufferedRequestMessages(socket, sessionId);
1407
1834
  }).catch((error) => {
1408
1835
  console.error("[debug] failed to start runtime debug session:", error);
@@ -1415,6 +1842,7 @@ async function startRuntimeDebugSession() {
1415
1842
  }
1416
1843
  currentConnection = null;
1417
1844
  currentController = null;
1845
+ resetRuntimeDebugVariableMonitor();
1418
1846
  clearBufferedRequestMessages();
1419
1847
  void stopDebugSession();
1420
1848
  };
@@ -1441,6 +1869,10 @@ async function startRuntimeDebugSession() {
1441
1869
  handleRuntimeDebugRequest(socket, sessionId, message);
1442
1870
  return;
1443
1871
  }
1872
+ if (isUiReplaceMessage(message)) {
1873
+ handleRuntimeDebugRequest(socket, sessionId, message);
1874
+ return;
1875
+ }
1444
1876
  if (message.type && message.type !== "session:init") {
1445
1877
  console.debug("[debug] runtime received message:", message);
1446
1878
  }
@@ -1552,13 +1984,13 @@ async function terminateCurrentScenario() {
1552
1984
  await executePluginCommand("scenario", {
1553
1985
  subCommand: "terminateStory"
1554
1986
  });
1555
- clearDebugSessionRuntimeState();
1987
+ await clearLocalDebugSessionRuntimeState();
1556
1988
  } catch (error) {
1557
1989
  console.error("Failed to terminate scenario:", error);
1558
1990
  }
1559
1991
  }
1560
1992
  async function startScenarioSession(session, config) {
1561
- clearDebugSessionRuntimeState();
1993
+ await clearLocalDebugSessionRuntimeState();
1562
1994
  for (const story of config.stories) {
1563
1995
  if (!isCurrentScenarioSession(session)) return;
1564
1996
  try {
@@ -1674,49 +2106,31 @@ function useScenario(stories, startName, entryName, goNextOnLoad = false) {
1674
2106
 
1675
2107
  // src/hooks/useSoundEffect.ts
1676
2108
  var import_react4 = require("react");
1677
- var LOADED_SOUNDS = /* @__PURE__ */ new Set();
1678
2109
  function useSoundEffect(src) {
1679
2110
  (0, import_react4.useEffect)(() => {
1680
- const loadSound = async () => {
1681
- try {
1682
- await executePluginCommand("audio", {
1683
- subCommand: "load",
1684
- name: src,
1685
- src
1686
- });
1687
- } catch (error) {
1688
- console.error(`Failed to load sound effect ${src}:`, error);
1689
- }
1690
- LOADED_SOUNDS.add(src);
1691
- };
1692
- let needRelease = false;
1693
- if (!LOADED_SOUNDS.has(src)) {
1694
- void loadSound();
1695
- needRelease = true;
1696
- }
1697
2111
  return () => {
1698
- const releaseSound = () => {
1699
- try {
2112
+ try {
2113
+ if (src) {
1700
2114
  executePluginCommand("audio", {
1701
2115
  subCommand: "release",
1702
- name: src
2116
+ name: src,
2117
+ silentFail: true
1703
2118
  });
1704
- } catch (error) {
1705
- console.error(`Failed to release sound effect ${src}:`, error);
1706
2119
  }
1707
- LOADED_SOUNDS.delete(src);
1708
- };
1709
- if (needRelease) {
1710
- releaseSound();
2120
+ } catch (error) {
2121
+ console.error(`Failed to release sound effect ${src}:`, error);
1711
2122
  }
1712
2123
  };
1713
2124
  }, [src]);
1714
2125
  return (0, import_react4.useCallback)(() => {
1715
2126
  try {
1716
- executePluginCommand("audio", {
1717
- subCommand: "play",
1718
- name: src
1719
- });
2127
+ if (src) {
2128
+ executePluginCommand("audio", {
2129
+ subCommand: "loadAndPlay",
2130
+ name: src,
2131
+ src
2132
+ });
2133
+ }
1720
2134
  } catch (error) {
1721
2135
  console.error(`Failed to play sound effect ${src}:`, error);
1722
2136
  }
@@ -1725,25 +2139,25 @@ function useSoundEffect(src) {
1725
2139
 
1726
2140
  // src/hooks/useStage.ts
1727
2141
  var import_react5 = require("react");
1728
- var import_valtio3 = require("valtio");
2142
+ var import_valtio4 = require("valtio");
1729
2143
  var SKIP_INTERVAL_MS = 80;
1730
2144
  var DEFAULT_FAST_FORWARD_STEP_LIMIT = 1e4;
1731
2145
  var DEFAULT_FAST_FORWARD_TIME_LIMIT_MS = 3e3;
1732
2146
  var autoRuntime = {
1733
2147
  defaultTailMs: 0
1734
2148
  };
1735
- var skipState = (0, import_valtio3.proxy)({ active: false });
1736
- var autoState = (0, import_valtio3.proxy)({ active: false });
1737
- var fastForwardState = (0, import_valtio3.proxy)({ active: false });
1738
- var warpState = (0, import_valtio3.proxy)({ active: false });
2149
+ var skipState = (0, import_valtio4.proxy)({ active: false });
2150
+ var autoState = (0, import_valtio4.proxy)({ active: false });
2151
+ var fastForwardState = (0, import_valtio4.proxy)({ active: false });
2152
+ var warpState = (0, import_valtio4.proxy)({ active: false });
1739
2153
  function useIsSkipping() {
1740
- return (0, import_valtio3.useSnapshot)(skipState).active;
2154
+ return (0, import_valtio4.useSnapshot)(skipState).active;
1741
2155
  }
1742
2156
  function useIsAutoing() {
1743
- return (0, import_valtio3.useSnapshot)(autoState).active;
2157
+ return (0, import_valtio4.useSnapshot)(autoState).active;
1744
2158
  }
1745
2159
  function useIsSeeking() {
1746
- return (0, import_valtio3.useSnapshot)(fastForwardState).active;
2160
+ return (0, import_valtio4.useSnapshot)(fastForwardState).active;
1747
2161
  }
1748
2162
  function getSeekingType() {
1749
2163
  if (warpState.active) return "warp";