@momoyu-ink/kit 0.6.0 → 0.7.0

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,10 +401,290 @@ 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
- var JS_GLOBAL_KEYS_EXCLUDED = /* @__PURE__ */ new Set(["window", "globalThis", "ARCHIVE", "GLOBAL"]);
399
- JS_GLOBAL_KEYS_EXCLUDED.forEach((key) => JS_GLOBAL_KEYS.delete(key));
659
+ var JS_GLOBAL_KEYS_EXCLUDED = /* @__PURE__ */ new Set(["window", "globalThis", "LOCAL", "ARCHIVE", "GLOBAL"]);
660
+ JS_GLOBAL_KEYS_EXCLUDED.forEach((key) => {
661
+ JS_GLOBAL_KEYS.delete(key);
662
+ });
663
+ var localVariables = new Proxy(
664
+ {},
665
+ {
666
+ get(_, key) {
667
+ if (typeof key === "string") {
668
+ return executePluginCommand("scenario", {
669
+ subCommand: "getLocalVariable",
670
+ name: key
671
+ });
672
+ }
673
+ return void 0;
674
+ },
675
+ set(_, key, value) {
676
+ if (typeof key === "string") {
677
+ executePluginCommand("scenario", {
678
+ subCommand: "setLocalVariable",
679
+ name: key,
680
+ value
681
+ });
682
+ return true;
683
+ }
684
+ return false;
685
+ }
686
+ }
687
+ );
400
688
  var archiveVariables = new Proxy(
401
689
  {},
402
690
  {
@@ -416,6 +704,7 @@ var archiveVariables = new Proxy(
416
704
  name: key,
417
705
  value
418
706
  });
707
+ reportRuntimeDebugVariableSet("archive", key, value);
419
708
  return true;
420
709
  }
421
710
  return false;
@@ -441,6 +730,7 @@ var globalVariables = new Proxy(
441
730
  key,
442
731
  value
443
732
  });
733
+ reportRuntimeDebugVariableSet("global", key, value);
444
734
  return true;
445
735
  }
446
736
  return false;
@@ -448,15 +738,16 @@ var globalVariables = new Proxy(
448
738
  }
449
739
  );
450
740
  function safeEval(code, sandbox) {
451
- const proxy4 = new Proxy(sandbox, {
741
+ const proxy5 = new Proxy(sandbox, {
452
742
  has(target, key) {
453
743
  if (typeof key === "string" && JS_GLOBAL_KEYS.has(key)) {
454
744
  return false;
455
745
  }
456
- return key in target || key === "ARCHIVE" || key === "GLOBAL" || key === "window" || key === "globalThis";
746
+ return key in target || key === "LOCAL" || key === "ARCHIVE" || key === "GLOBAL" || key === "window" || key === "globalThis";
457
747
  },
458
748
  get(target, key) {
459
- if (key === "window" || key === "globalThis") return proxy4;
749
+ if (key === "window" || key === "globalThis") return proxy5;
750
+ if (key === "LOCAL") return localVariables;
460
751
  if (key === "ARCHIVE") return archiveVariables;
461
752
  if (key === "GLOBAL") return globalVariables;
462
753
  if (key === Symbol.unscopables) return void 0;
@@ -467,7 +758,7 @@ function safeEval(code, sandbox) {
467
758
  return true;
468
759
  }
469
760
  });
470
- return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy4)(proxy4);
761
+ return new Function("sandbox", `with(sandbox) { return (${code}) }`).bind(proxy5)(proxy5);
471
762
  }
472
763
  globalThis.__moyu_eval_sandbox = (code) => {
473
764
  try {
@@ -655,6 +946,7 @@ import { proxy, useSnapshot } from "valtio";
655
946
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
656
947
  var PageParamsContext = createContext(void 0);
657
948
  var OverlayParamsContext = createContext(void 0);
949
+ var DEBUG_EMPTY_PAGE = "debug-empty";
658
950
  var overlayIdCounter = 0;
659
951
  var generateOverlayId = () => {
660
952
  return `overlay-${++overlayIdCounter}-${Date.now()}`;
@@ -682,6 +974,12 @@ function createStackNavigator(options) {
682
974
  for (const [key, config] of Object.entries(options.pages)) {
683
975
  normalizedPages[key] = normalizeDescriptor(config);
684
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
+ };
685
983
  const normalizedOverlays = {};
686
984
  if (options.overlays) {
687
985
  for (const [key, config] of Object.entries(options.overlays)) {
@@ -711,6 +1009,9 @@ function createStackNavigator(options) {
711
1009
  hasPage: (page) => {
712
1010
  return normalizedPages[page] !== void 0;
713
1011
  },
1012
+ hasOverlay: (overlay) => {
1013
+ return normalizedOverlays[overlay] !== void 0;
1014
+ },
714
1015
  goBack: () => {
715
1016
  console.warn("goBack() not implemented yet");
716
1017
  },
@@ -798,6 +1099,7 @@ var debugState = proxy2({
798
1099
  active: false,
799
1100
  checkpoints: {},
800
1101
  currentCursor: null,
1102
+ currentStory: null,
801
1103
  restoring: false,
802
1104
  lastError: null
803
1105
  });
@@ -841,9 +1143,37 @@ function reportDebugSessionError(error) {
841
1143
  function clearLocalRuntimeState() {
842
1144
  debugState.checkpoints = {};
843
1145
  debugState.currentCursor = null;
1146
+ debugState.currentStory = null;
844
1147
  debugState.restoring = false;
845
1148
  debugState.lastError = null;
846
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
+ }
847
1177
  async function doSettleActiveSeek(op, clearSlot, error) {
848
1178
  if (!op) {
849
1179
  return;
@@ -896,6 +1226,7 @@ async function syncLocalWarpCheckpoint(targetMarkerId) {
896
1226
  appState: appStateAdapter ? await appStateAdapter.capture() : void 0
897
1227
  };
898
1228
  debugState.currentCursor = checkpointCursor;
1229
+ debugState.currentStory = checkpointCursor.story;
899
1230
  debugState.checkpoints[targetMarkerId] = checkpoint;
900
1231
  await currentConfig.onMarkerEnter(checkpoint);
901
1232
  }
@@ -919,9 +1250,16 @@ function notifyMarkerEnter(checkpoint) {
919
1250
  reportDebugSessionError(error);
920
1251
  }
921
1252
  }
922
- async function prepareJumpStart(markerId) {
1253
+ async function prepareJumpStart(markerId, options) {
1254
+ const currentStory = debugState.currentStory ?? debugState.currentCursor?.story ?? null;
1255
+ const targetStory = options?.story;
1256
+ let restartedFromHead = false;
1257
+ if (targetStory && targetStory !== currentStory) {
1258
+ await restartJumpStoryFromHead(targetStory, currentStory);
1259
+ restartedFromHead = true;
1260
+ }
923
1261
  const checkpoint = debugState.checkpoints[markerId];
924
- if (checkpoint) {
1262
+ if (!restartedFromHead && checkpoint && (!targetStory || checkpoint.cursor.story === targetStory)) {
925
1263
  const restored2 = await executeScenarioCommand({
926
1264
  subCommand: "restoreCheckpoint",
927
1265
  key: markerId
@@ -933,6 +1271,7 @@ async function prepareJumpStart(markerId) {
933
1271
  await appStateAdapter.restore(checkpoint.appState);
934
1272
  }
935
1273
  debugState.currentCursor = checkpoint.cursor;
1274
+ debugState.currentStory = checkpoint.cursor.story;
936
1275
  return false;
937
1276
  }
938
1277
  const lookup = await executeScenarioCommand({
@@ -940,16 +1279,15 @@ async function prepareJumpStart(markerId) {
940
1279
  key: markerId
941
1280
  });
942
1281
  if (!lookup.markerExists) {
943
- throw new Error(`Marker ${markerId} is not available in the current story`);
1282
+ throw new Error(
1283
+ targetStory ? `Marker ${markerId} is not available in story ${targetStory}` : `Marker ${markerId} is not available in the current story`
1284
+ );
944
1285
  }
945
1286
  const startCheckpointKey = lookup.checkpointKey ?? null;
946
1287
  if (!startCheckpointKey) {
947
- if (!appStateAdapter?.restartCurrentStoryFromHead) {
948
- throw new Error("Restarting the current story from head is not supported by the app state adapter");
1288
+ if (!restartedFromHead) {
1289
+ await restartJumpStoryFromHead(targetStory, currentStory);
949
1290
  }
950
- await appStateAdapter.restartCurrentStoryFromHead();
951
- debugState.checkpoints = {};
952
- debugState.currentCursor = null;
953
1291
  return true;
954
1292
  }
955
1293
  const startCheckpoint = debugState.checkpoints[startCheckpointKey];
@@ -967,6 +1305,7 @@ async function prepareJumpStart(markerId) {
967
1305
  await appStateAdapter.restore(startCheckpoint.appState);
968
1306
  }
969
1307
  debugState.currentCursor = startCheckpoint.cursor;
1308
+ debugState.currentStory = startCheckpoint.cursor.story;
970
1309
  return true;
971
1310
  }
972
1311
  function handleMarkerEnter(marker) {
@@ -979,6 +1318,7 @@ function handleMarkerEnter(marker) {
979
1318
  appState: captureCheckpointAppStateSync()
980
1319
  };
981
1320
  debugState.currentCursor = checkpoint.cursor;
1321
+ debugState.currentStory = checkpoint.cursor.story;
982
1322
  debugState.checkpoints[marker.markerId] = checkpoint;
983
1323
  notifyMarkerEnter(checkpoint);
984
1324
  if (activeFastForward?.targetMarkerId === marker.markerId) {
@@ -1039,11 +1379,11 @@ async function runWarp(options) {
1039
1379
  await completion;
1040
1380
  }
1041
1381
  var debugSessionController = {
1042
- async restoreCheckpoint(markerId) {
1382
+ async restoreCheckpoint(markerId, options) {
1043
1383
  debugState.restoring = true;
1044
1384
  debugState.lastError = null;
1045
1385
  try {
1046
- const needsFastForward = await enqueueDebugOperation(async () => prepareJumpStart(markerId));
1386
+ const needsFastForward = await enqueueDebugOperation(async () => prepareJumpStart(markerId, options));
1047
1387
  if (needsFastForward) {
1048
1388
  await runFastForwardToMarker(markerId);
1049
1389
  }
@@ -1059,7 +1399,7 @@ var debugSessionController = {
1059
1399
  debugState.restoring = true;
1060
1400
  debugState.lastError = null;
1061
1401
  try {
1062
- const needsWarp = await enqueueDebugOperation(async () => prepareJumpStart(options.markerId));
1402
+ const needsWarp = await enqueueDebugOperation(async () => prepareJumpStart(options.markerId, options));
1063
1403
  if (needsWarp) {
1064
1404
  await runWarp(options);
1065
1405
  }
@@ -1070,14 +1410,26 @@ var debugSessionController = {
1070
1410
  debugState.restoring = false;
1071
1411
  }
1072
1412
  },
1073
- async switchPage(page, params) {
1413
+ async switchRoute(routeName, params) {
1074
1414
  return enqueueDebugOperation(async () => {
1075
1415
  debugState.lastError = null;
1076
1416
  try {
1077
- if (!appStateAdapter?.switchPage) {
1078
- throw new Error("Page switching is not supported by the app state adapter");
1417
+ const navigator = getNavigator();
1418
+ if (navigator.hasPage(routeName)) {
1419
+ if (!appStateAdapter?.switchPage) {
1420
+ throw new Error("Page switching is not supported by the app state adapter");
1421
+ }
1422
+ await appStateAdapter.switchPage(routeName, params);
1423
+ return;
1079
1424
  }
1080
- await appStateAdapter.switchPage(page, params);
1425
+ if (navigator.hasOverlay(routeName)) {
1426
+ if (!appStateAdapter?.switchOverlay) {
1427
+ throw new Error("Overlay switching is not supported by the app state adapter");
1428
+ }
1429
+ await appStateAdapter.switchOverlay(routeName, params);
1430
+ return;
1431
+ }
1432
+ throw new Error(`Route "${routeName}" not found`);
1081
1433
  } catch (error) {
1082
1434
  debugState.lastError = toErrorMessage(error);
1083
1435
  throw error;
@@ -1142,6 +1494,7 @@ async function startDebugSession(config = {}) {
1142
1494
  debugState.currentCursor = await executeScenarioCommand({
1143
1495
  subCommand: "getExecutionCursor"
1144
1496
  });
1497
+ debugState.currentStory = debugState.currentCursor?.story ?? null;
1145
1498
  } catch (error) {
1146
1499
  debugState.active = false;
1147
1500
  clearLocalRuntimeState();
@@ -1202,10 +1555,83 @@ function useDebugSession() {
1202
1555
  stopDebugSession,
1203
1556
  restoreCheckpoint: debugSessionController.restoreCheckpoint,
1204
1557
  warp: debugSessionController.warp,
1205
- switchPage: debugSessionController.switchPage
1558
+ switchRoute: debugSessionController.switchRoute
1206
1559
  };
1207
1560
  }
1208
1561
 
1562
+ // src/ui.ts
1563
+ import { proxy as proxy3, useSnapshot as useSnapshot3 } from "valtio";
1564
+ var UI_DATA_ASSET_PATH = "assets:///data/ui.json";
1565
+ var activeUiSchema = null;
1566
+ var uiDataState = proxy3({});
1567
+ var uiRuntimeState = proxy3({
1568
+ initialized: false,
1569
+ error: null
1570
+ });
1571
+ function applyUiData(nextData) {
1572
+ for (const key of Object.keys(uiDataState)) {
1573
+ delete uiDataState[key];
1574
+ }
1575
+ Object.assign(uiDataState, nextData);
1576
+ uiRuntimeState.initialized = true;
1577
+ uiRuntimeState.error = null;
1578
+ }
1579
+ async function initUiData(schema) {
1580
+ uiRuntimeState.initialized = false;
1581
+ uiRuntimeState.error = null;
1582
+ activeUiSchema = schema;
1583
+ let rawData;
1584
+ try {
1585
+ const rawText = await Promise.resolve(
1586
+ executePluginCommand("system", {
1587
+ subCommand: "readFile",
1588
+ path: UI_DATA_ASSET_PATH
1589
+ })
1590
+ );
1591
+ rawData = JSON.parse(rawText);
1592
+ } catch (error) {
1593
+ const message = error instanceof Error ? error.message : String(error);
1594
+ uiRuntimeState.error = message;
1595
+ throw new Error(`Failed to initialize UI data: ${message}`);
1596
+ }
1597
+ const parsed = schema.safeParse(rawData);
1598
+ if (!parsed.success) {
1599
+ uiRuntimeState.error = parsed.error.message;
1600
+ throw new Error(`Invalid UI data: ${parsed.error.message}`);
1601
+ }
1602
+ applyUiData(parsed.data);
1603
+ }
1604
+ function replaceUiData(rawData) {
1605
+ if (!activeUiSchema) {
1606
+ throw new Error("UI data schema has not been initialized. Call initUiData() first.");
1607
+ }
1608
+ const parsed = activeUiSchema.safeParse(rawData);
1609
+ if (!parsed.success) {
1610
+ uiRuntimeState.error = parsed.error.message;
1611
+ throw new Error(`Invalid replacement UI data: ${parsed.error.message}`);
1612
+ }
1613
+ applyUiData(parsed.data);
1614
+ }
1615
+ function getUiData(name) {
1616
+ if (!uiRuntimeState.initialized) {
1617
+ throw new Error("UI data has not been initialized. Call initUiData() before reading it.");
1618
+ }
1619
+ if (!(name in uiDataState)) {
1620
+ throw new Error(`UI data "${name}" is not available.`);
1621
+ }
1622
+ return uiDataState[name];
1623
+ }
1624
+ function useUiData(name) {
1625
+ if (!uiRuntimeState.initialized) {
1626
+ throw new Error("UI data has not been initialized. Call initUiData() before rendering UI pages.");
1627
+ }
1628
+ const snapshot = useSnapshot3(uiDataState);
1629
+ if (!(name in snapshot)) {
1630
+ throw new Error(`UI data "${name}" is not available.`);
1631
+ }
1632
+ return snapshot[name];
1633
+ }
1634
+
1209
1635
  // src/debug/runtime.ts
1210
1636
  var currentConnection = null;
1211
1637
  var currentController = null;
@@ -1214,11 +1640,14 @@ function getRuntimeWebSocket() {
1214
1640
  return globalThis.WebSocket ?? null;
1215
1641
  }
1216
1642
  function isJumpRequestMessage(message) {
1217
- 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");
1643
+ 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");
1218
1644
  }
1219
1645
  function isRouteRequestMessage(message) {
1220
1646
  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));
1221
1647
  }
1648
+ function isUiReplaceMessage(message) {
1649
+ return message.type === "ui:replace" && typeof message.sessionId === "string";
1650
+ }
1222
1651
  function sendRuntimeDebugMessage(socket, message) {
1223
1652
  const RuntimeWebSocket = getRuntimeWebSocket();
1224
1653
  if (!RuntimeWebSocket || socket.readyState !== RuntimeWebSocket.OPEN) {
@@ -1239,18 +1668,37 @@ function flushBufferedRequestMessages(socket, sessionId) {
1239
1668
  handleRuntimeDebugRequest(socket, sessionId, message);
1240
1669
  }
1241
1670
  }
1671
+ async function syncRuntimeDebugVariablesSnapshotSafe(reason) {
1672
+ try {
1673
+ await syncRuntimeDebugVariablesSnapshot(reason);
1674
+ } catch (error) {
1675
+ console.error("[debug] failed to sync runtime variables snapshot:", error);
1676
+ }
1677
+ }
1242
1678
  function handleRuntimeDebugRequest(socket, sessionId, message) {
1679
+ if (isUiReplaceMessage(message)) {
1680
+ try {
1681
+ replaceUiData(message.data);
1682
+ } catch (error) {
1683
+ console.error("[debug] failed to replace runtime UI data:", error);
1684
+ }
1685
+ return;
1686
+ }
1243
1687
  const controller = currentController;
1244
1688
  if (!controller) {
1245
1689
  bufferRequestMessage(message);
1246
1690
  return;
1247
1691
  }
1248
1692
  if (isJumpRequestMessage(message)) {
1693
+ setRuntimeDebugVariableEmissionSuspended(true);
1249
1694
  const runJump = message.strategy === "warp" ? controller.warp({
1250
1695
  markerId: message.markerId,
1696
+ story: message.story,
1251
1697
  boundary: message.boundary
1252
- }).then(() => true) : controller.restoreCheckpoint(message.markerId);
1253
- void runJump.then((restored) => {
1698
+ }).then(() => true) : controller.restoreCheckpoint(message.markerId, {
1699
+ story: message.story
1700
+ });
1701
+ void runJump.then(async (restored) => {
1254
1702
  if (!restored) {
1255
1703
  sendRuntimeDebugMessage(socket, {
1256
1704
  type: "jump:error",
@@ -1259,6 +1707,7 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1259
1707
  markerId: message.markerId,
1260
1708
  error: "Checkpoint not found"
1261
1709
  });
1710
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-error");
1262
1711
  return;
1263
1712
  }
1264
1713
  sendRuntimeDebugMessage(socket, {
@@ -1267,7 +1716,8 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1267
1716
  requestId: message.requestId,
1268
1717
  markerId: message.markerId
1269
1718
  });
1270
- }).catch((error) => {
1719
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-done");
1720
+ }).catch(async (error) => {
1271
1721
  sendRuntimeDebugMessage(socket, {
1272
1722
  type: "jump:error",
1273
1723
  sessionId,
@@ -1275,17 +1725,22 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1275
1725
  markerId: message.markerId,
1276
1726
  error: error instanceof Error ? error.message : String(error)
1277
1727
  });
1728
+ await syncRuntimeDebugVariablesSnapshotSafe("jump-error");
1729
+ }).finally(() => {
1730
+ setRuntimeDebugVariableEmissionSuspended(false);
1278
1731
  });
1279
1732
  return;
1280
1733
  }
1281
- void controller.switchPage(message.page, message.params).then(() => {
1734
+ setRuntimeDebugVariableEmissionSuspended(true);
1735
+ void controller.switchRoute(message.page, message.params).then(async () => {
1282
1736
  sendRuntimeDebugMessage(socket, {
1283
1737
  type: "route:done",
1284
1738
  sessionId,
1285
1739
  requestId: message.requestId,
1286
1740
  page: message.page
1287
1741
  });
1288
- }).catch((error) => {
1742
+ await syncRuntimeDebugVariablesSnapshotSafe("route-done");
1743
+ }).catch(async (error) => {
1289
1744
  sendRuntimeDebugMessage(socket, {
1290
1745
  type: "route:error",
1291
1746
  sessionId,
@@ -1293,6 +1748,9 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1293
1748
  page: message.page,
1294
1749
  error: error instanceof Error ? error.message : String(error)
1295
1750
  });
1751
+ await syncRuntimeDebugVariablesSnapshotSafe("route-error");
1752
+ }).finally(() => {
1753
+ setRuntimeDebugVariableEmissionSuspended(false);
1296
1754
  });
1297
1755
  }
1298
1756
  async function readDebugParams() {
@@ -1317,6 +1775,7 @@ async function stopRuntimeDebugSession() {
1317
1775
  const connection = currentConnection;
1318
1776
  currentConnection = null;
1319
1777
  currentController = null;
1778
+ resetRuntimeDebugVariableMonitor();
1320
1779
  clearBufferedRequestMessages();
1321
1780
  const RuntimeWebSocket = getRuntimeWebSocket();
1322
1781
  await stopDebugSession();
@@ -1363,6 +1822,13 @@ async function startRuntimeDebugSession() {
1363
1822
  }
1364
1823
  }).then((controller) => {
1365
1824
  currentController = controller;
1825
+ setRuntimeDebugVariableMessageSender((message) => {
1826
+ sendRuntimeDebugMessage(socket, {
1827
+ ...message,
1828
+ sessionId
1829
+ });
1830
+ });
1831
+ void syncRuntimeDebugVariablesSnapshotSafe("init");
1366
1832
  flushBufferedRequestMessages(socket, sessionId);
1367
1833
  }).catch((error) => {
1368
1834
  console.error("[debug] failed to start runtime debug session:", error);
@@ -1375,6 +1841,7 @@ async function startRuntimeDebugSession() {
1375
1841
  }
1376
1842
  currentConnection = null;
1377
1843
  currentController = null;
1844
+ resetRuntimeDebugVariableMonitor();
1378
1845
  clearBufferedRequestMessages();
1379
1846
  void stopDebugSession();
1380
1847
  };
@@ -1401,6 +1868,10 @@ async function startRuntimeDebugSession() {
1401
1868
  handleRuntimeDebugRequest(socket, sessionId, message);
1402
1869
  return;
1403
1870
  }
1871
+ if (isUiReplaceMessage(message)) {
1872
+ handleRuntimeDebugRequest(socket, sessionId, message);
1873
+ return;
1874
+ }
1404
1875
  if (message.type && message.type !== "session:init") {
1405
1876
  console.debug("[debug] runtime received message:", message);
1406
1877
  }
@@ -1634,49 +2105,31 @@ function useScenario(stories, startName, entryName, goNextOnLoad = false) {
1634
2105
 
1635
2106
  // src/hooks/useSoundEffect.ts
1636
2107
  import { useCallback, useEffect as useEffect2 } from "react";
1637
- var LOADED_SOUNDS = /* @__PURE__ */ new Set();
1638
2108
  function useSoundEffect(src) {
1639
2109
  useEffect2(() => {
1640
- const loadSound = async () => {
1641
- try {
1642
- await executePluginCommand("audio", {
1643
- subCommand: "load",
1644
- name: src,
1645
- src
1646
- });
1647
- } catch (error) {
1648
- console.error(`Failed to load sound effect ${src}:`, error);
1649
- }
1650
- LOADED_SOUNDS.add(src);
1651
- };
1652
- let needRelease = false;
1653
- if (!LOADED_SOUNDS.has(src)) {
1654
- void loadSound();
1655
- needRelease = true;
1656
- }
1657
2110
  return () => {
1658
- const releaseSound = () => {
1659
- try {
2111
+ try {
2112
+ if (src) {
1660
2113
  executePluginCommand("audio", {
1661
2114
  subCommand: "release",
1662
- name: src
2115
+ name: src,
2116
+ silentFail: true
1663
2117
  });
1664
- } catch (error) {
1665
- console.error(`Failed to release sound effect ${src}:`, error);
1666
2118
  }
1667
- LOADED_SOUNDS.delete(src);
1668
- };
1669
- if (needRelease) {
1670
- releaseSound();
2119
+ } catch (error) {
2120
+ console.error(`Failed to release sound effect ${src}:`, error);
1671
2121
  }
1672
2122
  };
1673
2123
  }, [src]);
1674
2124
  return useCallback(() => {
1675
2125
  try {
1676
- executePluginCommand("audio", {
1677
- subCommand: "play",
1678
- name: src
1679
- });
2126
+ if (src) {
2127
+ executePluginCommand("audio", {
2128
+ subCommand: "loadAndPlay",
2129
+ name: src,
2130
+ src
2131
+ });
2132
+ }
1680
2133
  } catch (error) {
1681
2134
  console.error(`Failed to play sound effect ${src}:`, error);
1682
2135
  }
@@ -1685,25 +2138,25 @@ function useSoundEffect(src) {
1685
2138
 
1686
2139
  // src/hooks/useStage.ts
1687
2140
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, createElement, useCallback as useCallback2 } from "react";
1688
- import { proxy as proxy3, useSnapshot as useSnapshot3 } from "valtio";
2141
+ import { proxy as proxy4, useSnapshot as useSnapshot4 } from "valtio";
1689
2142
  var SKIP_INTERVAL_MS = 80;
1690
2143
  var DEFAULT_FAST_FORWARD_STEP_LIMIT = 1e4;
1691
2144
  var DEFAULT_FAST_FORWARD_TIME_LIMIT_MS = 3e3;
1692
2145
  var autoRuntime = {
1693
2146
  defaultTailMs: 0
1694
2147
  };
1695
- var skipState = proxy3({ active: false });
1696
- var autoState = proxy3({ active: false });
1697
- var fastForwardState = proxy3({ active: false });
1698
- var warpState = proxy3({ active: false });
2148
+ var skipState = proxy4({ active: false });
2149
+ var autoState = proxy4({ active: false });
2150
+ var fastForwardState = proxy4({ active: false });
2151
+ var warpState = proxy4({ active: false });
1699
2152
  function useIsSkipping() {
1700
- return useSnapshot3(skipState).active;
2153
+ return useSnapshot4(skipState).active;
1701
2154
  }
1702
2155
  function useIsAutoing() {
1703
- return useSnapshot3(autoState).active;
2156
+ return useSnapshot4(autoState).active;
1704
2157
  }
1705
2158
  function useIsSeeking() {
1706
- return useSnapshot3(fastForwardState).active;
2159
+ return useSnapshot4(fastForwardState).active;
1707
2160
  }
1708
2161
  function getSeekingType() {
1709
2162
  if (warpState.active) return "warp";
@@ -2409,6 +2862,7 @@ function useBeforeHandleCommandCallback(callback) {
2409
2862
  // src/lib.ts
2410
2863
  export * from "@react-spring/core";
2411
2864
  export {
2865
+ DEBUG_EMPTY_PAGE,
2412
2866
  OverlayParamsContext,
2413
2867
  PageParamsContext,
2414
2868
  StageContextProvider,
@@ -2421,6 +2875,7 @@ export {
2421
2875
  createStackNavigator,
2422
2876
  createStage,
2423
2877
  createStaticNavigation,
2878
+ ensureArchiveVariableDefaults,
2424
2879
  executeNodeCommand,
2425
2880
  executePluginCommand,
2426
2881
  fastForwardState,
@@ -2428,11 +2883,16 @@ export {
2428
2883
  getNavigator,
2429
2884
  getSeekingType,
2430
2885
  getStageSize,
2886
+ getUiData,
2431
2887
  globalEventListeners,
2432
2888
  globalRequestAnimationFrameListeners,
2889
+ initGlobalVariableDefaults,
2890
+ initUiData,
2891
+ loadVariablesDocument,
2433
2892
  moyu_exports as moyu,
2434
2893
  nextLine,
2435
2894
  registerAppStateAdapter,
2895
+ replaceUiData,
2436
2896
  setDefaultAutoTailMs,
2437
2897
  setWaiting,
2438
2898
  skipState,
@@ -2459,5 +2919,6 @@ export {
2459
2919
  useSkipCallback,
2460
2920
  useSoundEffect,
2461
2921
  useStageContext,
2922
+ useUiData,
2462
2923
  warpState
2463
2924
  };