@momoyu-ink/kit 0.7.1 → 0.8.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.
@@ -134,6 +134,10 @@ type JsonValue = number | string | boolean | Array<JsonValue> | {
134
134
  type ScenarioCommand = {
135
135
  subCommand: 'addStory';
136
136
  name: string;
137
+ } | {
138
+ subCommand: 'replaceStoryData';
139
+ name: string;
140
+ content: string;
137
141
  } | {
138
142
  subCommand: 'removeStory';
139
143
  name: string;
@@ -134,6 +134,10 @@ type JsonValue = number | string | boolean | Array<JsonValue> | {
134
134
  type ScenarioCommand = {
135
135
  subCommand: 'addStory';
136
136
  name: string;
137
+ } | {
138
+ subCommand: 'replaceStoryData';
139
+ name: string;
140
+ content: string;
137
141
  } | {
138
142
  subCommand: 'removeStory';
139
143
  name: string;
@@ -1,4 +1,4 @@
1
1
  export { Fragment, jsxDEV } from 'react/jsx-dev-runtime';
2
- export { J as JSX } from './jsx-dev-runtime-CKclO27C.mjs';
2
+ export { J as JSX } from './jsx-dev-runtime-BCxm528Z.mjs';
3
3
  import 'react/jsx-runtime';
4
4
  import 'react';
@@ -1,4 +1,4 @@
1
1
  export { Fragment, jsxDEV } from 'react/jsx-dev-runtime';
2
- export { J as JSX } from './jsx-dev-runtime-CKclO27C.js';
2
+ export { J as JSX } from './jsx-dev-runtime-BCxm528Z.js';
3
3
  import 'react/jsx-runtime';
4
4
  import 'react';
@@ -1,4 +1,4 @@
1
1
  export { Fragment, jsx, jsxs } from 'react/jsx-runtime';
2
- export { J as JSX } from './jsx-dev-runtime-CKclO27C.mjs';
2
+ export { J as JSX } from './jsx-dev-runtime-BCxm528Z.mjs';
3
3
  import 'react/jsx-dev-runtime';
4
4
  import 'react';
@@ -1,4 +1,4 @@
1
1
  export { Fragment, jsx, jsxs } from 'react/jsx-runtime';
2
- export { J as JSX } from './jsx-dev-runtime-CKclO27C.js';
2
+ export { J as JSX } from './jsx-dev-runtime-BCxm528Z.js';
3
3
  import 'react/jsx-dev-runtime';
4
4
  import 'react';
package/dist/lib.cjs CHANGED
@@ -1027,13 +1027,20 @@ function createStackNavigator(options) {
1027
1027
  goBack: () => {
1028
1028
  console.warn("goBack() not implemented yet");
1029
1029
  },
1030
- pushOverlay: (overlay, params) => {
1030
+ hasActiveOverlay: (overlay, id) => {
1031
+ if (id) {
1032
+ console.log(navigationState.overlayStack.some((o) => o.type === overlay && o.id === id));
1033
+ return navigationState.overlayStack.some((o) => o.type === overlay && o.id === id);
1034
+ }
1035
+ return navigationState.overlayStack.some((o) => o.type === overlay);
1036
+ },
1037
+ pushOverlay: (overlay, params, _id) => {
1031
1038
  if (!normalizedOverlays[overlay]) {
1032
1039
  console.error(`Navigation Error: Overlay "${overlay}" not found in navigator configuration.`);
1033
1040
  return;
1034
1041
  }
1035
1042
  validateParams("overlay", overlay, normalizedOverlays[overlay], params);
1036
- const id = generateOverlayId();
1043
+ const id = _id ?? generateOverlayId();
1037
1044
  const overlayInfo = { type: overlay, id };
1038
1045
  navigationState.overlayStack.push(overlayInfo);
1039
1046
  if (params !== void 0) {
@@ -1222,6 +1229,36 @@ async function clearLocalDebugSessionRuntimeState() {
1222
1229
  async function clearRemoteCheckpoints() {
1223
1230
  await executeScenarioCommand({ subCommand: "clearCheckpoints" });
1224
1231
  }
1232
+ function pruneLocalStoryCheckpoints(story, preserved) {
1233
+ debugState.checkpoints = Object.fromEntries(
1234
+ Object.entries(debugState.checkpoints).filter(([key, checkpoint]) => {
1235
+ if (checkpoint.cursor.story !== story) {
1236
+ return true;
1237
+ }
1238
+ return preserved.has(key);
1239
+ })
1240
+ );
1241
+ }
1242
+ async function syncCurrentExecutionCursor(fallbackStory = null) {
1243
+ debugState.currentCursor = await executeScenarioCommand({
1244
+ subCommand: "getExecutionCursor"
1245
+ });
1246
+ debugState.currentStory = debugState.currentCursor?.story ?? fallbackStory;
1247
+ }
1248
+ async function executeRestartBoundary(boundary, story) {
1249
+ if (boundary.kind === "checkpoint") {
1250
+ await debugSessionController.restoreCheckpoint(boundary.checkpointKey, { story });
1251
+ return;
1252
+ }
1253
+ if (!appStateAdapter?.restartStoryFromHead) {
1254
+ throw new Error("Restarting a story from an explicit entry is not supported by the app state adapter");
1255
+ }
1256
+ await appStateAdapter.restartStoryFromHead({
1257
+ story: boundary.story,
1258
+ entry: boundary.kind === "restart-story" ? boundary.entry : boundary.paragraph
1259
+ });
1260
+ await syncCurrentExecutionCursor(boundary.story);
1261
+ }
1225
1262
  async function syncLocalWarpCheckpoint(targetMarkerId) {
1226
1263
  const cursor = await executeScenarioCommand({
1227
1264
  subCommand: "getExecutionCursor"
@@ -1281,15 +1318,17 @@ async function prepareJumpStart(markerId, options) {
1281
1318
  subCommand: "restoreCheckpoint",
1282
1319
  key: markerId
1283
1320
  });
1284
- if (!restored2) {
1285
- throw new Error(`Failed to restore checkpoint ${markerId}`);
1286
- }
1287
- if (appStateAdapter) {
1288
- await appStateAdapter.restore(checkpoint.appState);
1321
+ if (restored2) {
1322
+ if (appStateAdapter) {
1323
+ await appStateAdapter.restore(checkpoint.appState);
1324
+ }
1325
+ debugState.currentCursor = checkpoint.cursor;
1326
+ debugState.currentStory = checkpoint.cursor.story;
1327
+ return false;
1289
1328
  }
1290
- debugState.currentCursor = checkpoint.cursor;
1291
- debugState.currentStory = checkpoint.cursor.story;
1292
- return false;
1329
+ debugState.checkpoints = Object.fromEntries(
1330
+ Object.entries(debugState.checkpoints).filter(([key]) => key !== markerId)
1331
+ );
1293
1332
  }
1294
1333
  const lookup = await executeScenarioCommand({
1295
1334
  subCommand: "getFastForwardCheckpoint",
@@ -1309,7 +1348,8 @@ async function prepareJumpStart(markerId, options) {
1309
1348
  }
1310
1349
  const startCheckpoint = debugState.checkpoints[startCheckpointKey];
1311
1350
  if (!startCheckpoint) {
1312
- throw new Error(`Local checkpoint ${startCheckpointKey} not found`);
1351
+ await restartJumpStoryFromHead(targetStory, currentStory);
1352
+ return true;
1313
1353
  }
1314
1354
  const restored = await executeScenarioCommand({
1315
1355
  subCommand: "restoreCheckpoint",
@@ -1452,6 +1492,46 @@ var debugSessionController = {
1452
1492
  throw error;
1453
1493
  }
1454
1494
  });
1495
+ },
1496
+ async replaceStory(options) {
1497
+ debugState.restoring = true;
1498
+ debugState.lastError = null;
1499
+ try {
1500
+ const outcome = await enqueueDebugOperation(
1501
+ async () => executeScenarioCommand({
1502
+ subCommand: "replaceStoryData",
1503
+ name: options.story,
1504
+ content: options.content
1505
+ })
1506
+ );
1507
+ if (outcome.plan.mode === "invalidate-only") {
1508
+ pruneLocalStoryCheckpoints(options.story, /* @__PURE__ */ new Set());
1509
+ }
1510
+ if (outcome.plan.mode === "reposition") {
1511
+ if (!outcome.plan.boundary) {
1512
+ throw new Error("Story replace outcome is missing an executable boundary");
1513
+ }
1514
+ const seekMode = options.seekMode ?? "warp";
1515
+ const replaceTargetMarkerId = options.targetMarkerId ?? outcome.plan.targetMarkerId;
1516
+ await executeRestartBoundary(outcome.plan.boundary, options.story);
1517
+ if (replaceTargetMarkerId && !(outcome.plan.boundary.kind === "checkpoint" && outcome.plan.boundary.checkpointKey === replaceTargetMarkerId)) {
1518
+ if (seekMode === "warp") {
1519
+ await runWarp({
1520
+ markerId: replaceTargetMarkerId,
1521
+ boundary: "after"
1522
+ });
1523
+ } else if (seekMode === "fast-forward") {
1524
+ await runFastForwardToMarker(replaceTargetMarkerId);
1525
+ }
1526
+ }
1527
+ }
1528
+ return outcome;
1529
+ } catch (error) {
1530
+ debugState.lastError = toErrorMessage(error);
1531
+ throw error;
1532
+ } finally {
1533
+ debugState.restoring = false;
1534
+ }
1455
1535
  }
1456
1536
  };
1457
1537
  function registerAppStateAdapter(adapter) {
@@ -1556,7 +1636,8 @@ function useDebugSession() {
1556
1636
  stopDebugSession,
1557
1637
  restoreCheckpoint: debugSessionController.restoreCheckpoint,
1558
1638
  warp: debugSessionController.warp,
1559
- switchRoute: debugSessionController.switchRoute
1639
+ switchRoute: debugSessionController.switchRoute,
1640
+ replaceStory: debugSessionController.replaceStory
1560
1641
  };
1561
1642
  }
1562
1643
 
@@ -1646,6 +1727,9 @@ function isJumpRequestMessage(message) {
1646
1727
  function isRouteRequestMessage(message) {
1647
1728
  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));
1648
1729
  }
1730
+ function isStoryReplaceMessage(message) {
1731
+ return message.type === "story:replace" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.story === "string" && typeof message.content === "string" && (message.seekMode === void 0 || message.seekMode === "off" || message.seekMode === "fast-forward" || message.seekMode === "warp") && (message.targetMarkerId === void 0 || typeof message.targetMarkerId === "string");
1732
+ }
1649
1733
  function isUiReplaceMessage(message) {
1650
1734
  return message.type === "ui:replace" && typeof message.sessionId === "string";
1651
1735
  }
@@ -1732,6 +1816,35 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1732
1816
  });
1733
1817
  return;
1734
1818
  }
1819
+ if (isStoryReplaceMessage(message)) {
1820
+ setRuntimeDebugVariableEmissionSuspended(true);
1821
+ void controller.replaceStory({
1822
+ story: message.story,
1823
+ content: message.content,
1824
+ seekMode: message.seekMode,
1825
+ targetMarkerId: message.targetMarkerId
1826
+ }).then(async () => {
1827
+ sendRuntimeDebugMessage(socket, {
1828
+ type: "story:replace:done",
1829
+ sessionId,
1830
+ requestId: message.requestId,
1831
+ story: message.story
1832
+ });
1833
+ await syncRuntimeDebugVariablesSnapshotSafe("story-replace-done");
1834
+ }).catch(async (error) => {
1835
+ sendRuntimeDebugMessage(socket, {
1836
+ type: "story:replace:error",
1837
+ sessionId,
1838
+ requestId: message.requestId,
1839
+ story: message.story,
1840
+ error: error instanceof Error ? error.message : String(error)
1841
+ });
1842
+ await syncRuntimeDebugVariablesSnapshotSafe("story-replace-error");
1843
+ }).finally(() => {
1844
+ setRuntimeDebugVariableEmissionSuspended(false);
1845
+ });
1846
+ return;
1847
+ }
1735
1848
  setRuntimeDebugVariableEmissionSuspended(true);
1736
1849
  void controller.switchRoute(message.page, message.params).then(async () => {
1737
1850
  sendRuntimeDebugMessage(socket, {
@@ -1869,6 +1982,10 @@ async function startRuntimeDebugSession() {
1869
1982
  handleRuntimeDebugRequest(socket, sessionId, message);
1870
1983
  return;
1871
1984
  }
1985
+ if (isStoryReplaceMessage(message)) {
1986
+ handleRuntimeDebugRequest(socket, sessionId, message);
1987
+ return;
1988
+ }
1872
1989
  if (isUiReplaceMessage(message)) {
1873
1990
  handleRuntimeDebugRequest(socket, sessionId, message);
1874
1991
  return;
package/dist/lib.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactElement, Component, ForwardRefExoticComponent, CSSProperties, FC, ComponentType } from 'react';
3
- import { M as MouseEvent, a as MoyuEvent, C as Command, b as MaybePromise, J as JSX, c as JsonValue } from './jsx-dev-runtime-CKclO27C.mjs';
4
- export { A as AudioCommand, d as AudioSettings, B as BubbleEvent, D as DetailedMoyuProps, E as EffectParams, G as GamepadCommand, K as KeyboardEvent, e as KeyboardEventKind, f as MakeNullOptional, g as MouseEventKind, h as MoyuAnimationAttributes, i as MoyuBackdropAttributes, j as MoyuClipAttributes, k as MoyuContainerAttributes, l as MoyuEventHandler, m as MoyuFilterAttributes, n as MoyuListenerAttributes, o as MoyuNodeAttributes, p as MoyuSpriteAttributes, q as MoyuTextAttributes, r as MoyuVideoAttributes, N as Node, S as ScenarioCommand, s as SystemCommand, T as TextCommand, t as TouchEvent, u as TouchEventKind, v as Tuple2, w as Tuple3, x as Tuple4, W as WindowState, y as createBubbleEvent, z as getStageSize } from './jsx-dev-runtime-CKclO27C.mjs';
3
+ import { M as MouseEvent, a as MoyuEvent, C as Command, b as MaybePromise, J as JSX, c as JsonValue } from './jsx-dev-runtime-BCxm528Z.mjs';
4
+ export { A as AudioCommand, d as AudioSettings, B as BubbleEvent, D as DetailedMoyuProps, E as EffectParams, G as GamepadCommand, K as KeyboardEvent, e as KeyboardEventKind, f as MakeNullOptional, g as MouseEventKind, h as MoyuAnimationAttributes, i as MoyuBackdropAttributes, j as MoyuClipAttributes, k as MoyuContainerAttributes, l as MoyuEventHandler, m as MoyuFilterAttributes, n as MoyuListenerAttributes, o as MoyuNodeAttributes, p as MoyuSpriteAttributes, q as MoyuTextAttributes, r as MoyuVideoAttributes, N as Node, S as ScenarioCommand, s as SystemCommand, T as TextCommand, t as TouchEvent, u as TouchEventKind, v as Tuple2, w as Tuple3, x as Tuple4, W as WindowState, y as createBubbleEvent, z as getStageSize } from './jsx-dev-runtime-BCxm528Z.mjs';
5
5
  import { FluidValue } from '@react-spring/shared';
6
6
  import { ElementType, AssignableKeys, ComponentPropsWithRef } from '@react-spring/types';
7
7
  import { Snapshot } from 'valtio';
@@ -424,12 +424,19 @@ interface Navigator<PageName extends string = DefaultPageName, OverlayName exten
424
424
  * Go back to the previous page (not implemented yet)
425
425
  */
426
426
  goBack: () => void;
427
+ /**
428
+ * Check whether an overlay has been pushed, optionally by ID
429
+ * @param overlay Overlay name
430
+ * @param id Optional unique ID to check for specific overlay instance
431
+ */
432
+ hasActiveOverlay: (overlay: OverlayName, id?: string) => boolean;
427
433
  /**
428
434
  * Push an overlay to the overlay stack
429
435
  * @param overlay Overlay name
430
436
  * @param params Parameters to pass to the overlay (optional)
437
+ * @param id Unique ID for the overlay (optional)
431
438
  */
432
- pushOverlay: (overlay: OverlayName, params?: Record<string, any>) => void;
439
+ pushOverlay: (overlay: OverlayName, params?: Record<string, any>, id?: string) => void;
433
440
  /**
434
441
  * Pop the top overlay from the stack
435
442
  */
@@ -522,6 +529,34 @@ type ExecutionCursor = {
522
529
  markerId?: string;
523
530
  };
524
531
 
532
+ type ExecutableRestartBoundary = {
533
+ kind: 'checkpoint';
534
+ checkpointKey: string;
535
+ } | {
536
+ kind: 'restart-story';
537
+ story: string;
538
+ entry: string;
539
+ } | {
540
+ kind: 'restart-paragraph';
541
+ story: string;
542
+ paragraph: string;
543
+ };
544
+
545
+ type StoryReplaceMode = 'noop' | 'reposition' | 'invalidate-only';
546
+
547
+ type StoryReplaceExecutionPlan = {
548
+ mode: StoryReplaceMode;
549
+ boundary?: ExecutableRestartBoundary;
550
+ targetMarkerId?: string;
551
+ changedControlFlow: boolean;
552
+ };
553
+
554
+ type StoryReplaceOutcome = {
555
+ story: string;
556
+ currentStoryAffected: boolean;
557
+ plan: StoryReplaceExecutionPlan;
558
+ };
559
+
525
560
  /** Flow control interface passed to every handler. */
526
561
  interface GameControl {
527
562
  /** Timed wait — blocks auto-advance. Engine fires scenariowaitingcancelled on timeout or skip. */
@@ -695,6 +730,12 @@ interface DebugSessionController {
695
730
  restoreCheckpoint(markerId: string, options?: DebugJumpTargetOptions): Promise<boolean>;
696
731
  warp(options: DebugWarpOptions): Promise<void>;
697
732
  switchRoute(routeName: string, params?: Record<string, unknown>): Promise<void>;
733
+ replaceStory(options: {
734
+ story: string;
735
+ content: string;
736
+ seekMode?: 'off' | 'fast-forward' | 'warp';
737
+ targetMarkerId?: string;
738
+ }): Promise<StoryReplaceOutcome>;
698
739
  }
699
740
  interface DebugStoryStartOptions {
700
741
  story: string;
@@ -718,6 +759,12 @@ declare function useDebugSession(): {
718
759
  restoreCheckpoint: (markerId: string, options?: DebugJumpTargetOptions) => Promise<boolean>;
719
760
  warp: (options: DebugWarpOptions) => Promise<void>;
720
761
  switchRoute: (routeName: string, params?: Record<string, unknown>) => Promise<void>;
762
+ replaceStory: (options: {
763
+ story: string;
764
+ content: string;
765
+ seekMode?: "off" | "fast-forward" | "warp";
766
+ targetMarkerId?: string;
767
+ }) => Promise<StoryReplaceOutcome>;
721
768
  active: boolean;
722
769
  checkpoints: {
723
770
  readonly [x: string]: {
package/dist/lib.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactElement, Component, ForwardRefExoticComponent, CSSProperties, FC, ComponentType } from 'react';
3
- import { M as MouseEvent, a as MoyuEvent, C as Command, b as MaybePromise, J as JSX, c as JsonValue } from './jsx-dev-runtime-CKclO27C.js';
4
- export { A as AudioCommand, d as AudioSettings, B as BubbleEvent, D as DetailedMoyuProps, E as EffectParams, G as GamepadCommand, K as KeyboardEvent, e as KeyboardEventKind, f as MakeNullOptional, g as MouseEventKind, h as MoyuAnimationAttributes, i as MoyuBackdropAttributes, j as MoyuClipAttributes, k as MoyuContainerAttributes, l as MoyuEventHandler, m as MoyuFilterAttributes, n as MoyuListenerAttributes, o as MoyuNodeAttributes, p as MoyuSpriteAttributes, q as MoyuTextAttributes, r as MoyuVideoAttributes, N as Node, S as ScenarioCommand, s as SystemCommand, T as TextCommand, t as TouchEvent, u as TouchEventKind, v as Tuple2, w as Tuple3, x as Tuple4, W as WindowState, y as createBubbleEvent, z as getStageSize } from './jsx-dev-runtime-CKclO27C.js';
3
+ import { M as MouseEvent, a as MoyuEvent, C as Command, b as MaybePromise, J as JSX, c as JsonValue } from './jsx-dev-runtime-BCxm528Z.js';
4
+ export { A as AudioCommand, d as AudioSettings, B as BubbleEvent, D as DetailedMoyuProps, E as EffectParams, G as GamepadCommand, K as KeyboardEvent, e as KeyboardEventKind, f as MakeNullOptional, g as MouseEventKind, h as MoyuAnimationAttributes, i as MoyuBackdropAttributes, j as MoyuClipAttributes, k as MoyuContainerAttributes, l as MoyuEventHandler, m as MoyuFilterAttributes, n as MoyuListenerAttributes, o as MoyuNodeAttributes, p as MoyuSpriteAttributes, q as MoyuTextAttributes, r as MoyuVideoAttributes, N as Node, S as ScenarioCommand, s as SystemCommand, T as TextCommand, t as TouchEvent, u as TouchEventKind, v as Tuple2, w as Tuple3, x as Tuple4, W as WindowState, y as createBubbleEvent, z as getStageSize } from './jsx-dev-runtime-BCxm528Z.js';
5
5
  import { FluidValue } from '@react-spring/shared';
6
6
  import { ElementType, AssignableKeys, ComponentPropsWithRef } from '@react-spring/types';
7
7
  import { Snapshot } from 'valtio';
@@ -424,12 +424,19 @@ interface Navigator<PageName extends string = DefaultPageName, OverlayName exten
424
424
  * Go back to the previous page (not implemented yet)
425
425
  */
426
426
  goBack: () => void;
427
+ /**
428
+ * Check whether an overlay has been pushed, optionally by ID
429
+ * @param overlay Overlay name
430
+ * @param id Optional unique ID to check for specific overlay instance
431
+ */
432
+ hasActiveOverlay: (overlay: OverlayName, id?: string) => boolean;
427
433
  /**
428
434
  * Push an overlay to the overlay stack
429
435
  * @param overlay Overlay name
430
436
  * @param params Parameters to pass to the overlay (optional)
437
+ * @param id Unique ID for the overlay (optional)
431
438
  */
432
- pushOverlay: (overlay: OverlayName, params?: Record<string, any>) => void;
439
+ pushOverlay: (overlay: OverlayName, params?: Record<string, any>, id?: string) => void;
433
440
  /**
434
441
  * Pop the top overlay from the stack
435
442
  */
@@ -522,6 +529,34 @@ type ExecutionCursor = {
522
529
  markerId?: string;
523
530
  };
524
531
 
532
+ type ExecutableRestartBoundary = {
533
+ kind: 'checkpoint';
534
+ checkpointKey: string;
535
+ } | {
536
+ kind: 'restart-story';
537
+ story: string;
538
+ entry: string;
539
+ } | {
540
+ kind: 'restart-paragraph';
541
+ story: string;
542
+ paragraph: string;
543
+ };
544
+
545
+ type StoryReplaceMode = 'noop' | 'reposition' | 'invalidate-only';
546
+
547
+ type StoryReplaceExecutionPlan = {
548
+ mode: StoryReplaceMode;
549
+ boundary?: ExecutableRestartBoundary;
550
+ targetMarkerId?: string;
551
+ changedControlFlow: boolean;
552
+ };
553
+
554
+ type StoryReplaceOutcome = {
555
+ story: string;
556
+ currentStoryAffected: boolean;
557
+ plan: StoryReplaceExecutionPlan;
558
+ };
559
+
525
560
  /** Flow control interface passed to every handler. */
526
561
  interface GameControl {
527
562
  /** Timed wait — blocks auto-advance. Engine fires scenariowaitingcancelled on timeout or skip. */
@@ -695,6 +730,12 @@ interface DebugSessionController {
695
730
  restoreCheckpoint(markerId: string, options?: DebugJumpTargetOptions): Promise<boolean>;
696
731
  warp(options: DebugWarpOptions): Promise<void>;
697
732
  switchRoute(routeName: string, params?: Record<string, unknown>): Promise<void>;
733
+ replaceStory(options: {
734
+ story: string;
735
+ content: string;
736
+ seekMode?: 'off' | 'fast-forward' | 'warp';
737
+ targetMarkerId?: string;
738
+ }): Promise<StoryReplaceOutcome>;
698
739
  }
699
740
  interface DebugStoryStartOptions {
700
741
  story: string;
@@ -718,6 +759,12 @@ declare function useDebugSession(): {
718
759
  restoreCheckpoint: (markerId: string, options?: DebugJumpTargetOptions) => Promise<boolean>;
719
760
  warp: (options: DebugWarpOptions) => Promise<void>;
720
761
  switchRoute: (routeName: string, params?: Record<string, unknown>) => Promise<void>;
762
+ replaceStory: (options: {
763
+ story: string;
764
+ content: string;
765
+ seekMode?: "off" | "fast-forward" | "warp";
766
+ targetMarkerId?: string;
767
+ }) => Promise<StoryReplaceOutcome>;
721
768
  active: boolean;
722
769
  checkpoints: {
723
770
  readonly [x: string]: {
package/dist/lib.mjs CHANGED
@@ -1015,13 +1015,20 @@ function createStackNavigator(options) {
1015
1015
  goBack: () => {
1016
1016
  console.warn("goBack() not implemented yet");
1017
1017
  },
1018
- pushOverlay: (overlay, params) => {
1018
+ hasActiveOverlay: (overlay, id) => {
1019
+ if (id) {
1020
+ console.log(navigationState.overlayStack.some((o) => o.type === overlay && o.id === id));
1021
+ return navigationState.overlayStack.some((o) => o.type === overlay && o.id === id);
1022
+ }
1023
+ return navigationState.overlayStack.some((o) => o.type === overlay);
1024
+ },
1025
+ pushOverlay: (overlay, params, _id) => {
1019
1026
  if (!normalizedOverlays[overlay]) {
1020
1027
  console.error(`Navigation Error: Overlay "${overlay}" not found in navigator configuration.`);
1021
1028
  return;
1022
1029
  }
1023
1030
  validateParams("overlay", overlay, normalizedOverlays[overlay], params);
1024
- const id = generateOverlayId();
1031
+ const id = _id ?? generateOverlayId();
1025
1032
  const overlayInfo = { type: overlay, id };
1026
1033
  navigationState.overlayStack.push(overlayInfo);
1027
1034
  if (params !== void 0) {
@@ -1210,6 +1217,36 @@ async function clearLocalDebugSessionRuntimeState() {
1210
1217
  async function clearRemoteCheckpoints() {
1211
1218
  await executeScenarioCommand({ subCommand: "clearCheckpoints" });
1212
1219
  }
1220
+ function pruneLocalStoryCheckpoints(story, preserved) {
1221
+ debugState.checkpoints = Object.fromEntries(
1222
+ Object.entries(debugState.checkpoints).filter(([key, checkpoint]) => {
1223
+ if (checkpoint.cursor.story !== story) {
1224
+ return true;
1225
+ }
1226
+ return preserved.has(key);
1227
+ })
1228
+ );
1229
+ }
1230
+ async function syncCurrentExecutionCursor(fallbackStory = null) {
1231
+ debugState.currentCursor = await executeScenarioCommand({
1232
+ subCommand: "getExecutionCursor"
1233
+ });
1234
+ debugState.currentStory = debugState.currentCursor?.story ?? fallbackStory;
1235
+ }
1236
+ async function executeRestartBoundary(boundary, story) {
1237
+ if (boundary.kind === "checkpoint") {
1238
+ await debugSessionController.restoreCheckpoint(boundary.checkpointKey, { story });
1239
+ return;
1240
+ }
1241
+ if (!appStateAdapter?.restartStoryFromHead) {
1242
+ throw new Error("Restarting a story from an explicit entry is not supported by the app state adapter");
1243
+ }
1244
+ await appStateAdapter.restartStoryFromHead({
1245
+ story: boundary.story,
1246
+ entry: boundary.kind === "restart-story" ? boundary.entry : boundary.paragraph
1247
+ });
1248
+ await syncCurrentExecutionCursor(boundary.story);
1249
+ }
1213
1250
  async function syncLocalWarpCheckpoint(targetMarkerId) {
1214
1251
  const cursor = await executeScenarioCommand({
1215
1252
  subCommand: "getExecutionCursor"
@@ -1269,15 +1306,17 @@ async function prepareJumpStart(markerId, options) {
1269
1306
  subCommand: "restoreCheckpoint",
1270
1307
  key: markerId
1271
1308
  });
1272
- if (!restored2) {
1273
- throw new Error(`Failed to restore checkpoint ${markerId}`);
1274
- }
1275
- if (appStateAdapter) {
1276
- await appStateAdapter.restore(checkpoint.appState);
1309
+ if (restored2) {
1310
+ if (appStateAdapter) {
1311
+ await appStateAdapter.restore(checkpoint.appState);
1312
+ }
1313
+ debugState.currentCursor = checkpoint.cursor;
1314
+ debugState.currentStory = checkpoint.cursor.story;
1315
+ return false;
1277
1316
  }
1278
- debugState.currentCursor = checkpoint.cursor;
1279
- debugState.currentStory = checkpoint.cursor.story;
1280
- return false;
1317
+ debugState.checkpoints = Object.fromEntries(
1318
+ Object.entries(debugState.checkpoints).filter(([key]) => key !== markerId)
1319
+ );
1281
1320
  }
1282
1321
  const lookup = await executeScenarioCommand({
1283
1322
  subCommand: "getFastForwardCheckpoint",
@@ -1297,7 +1336,8 @@ async function prepareJumpStart(markerId, options) {
1297
1336
  }
1298
1337
  const startCheckpoint = debugState.checkpoints[startCheckpointKey];
1299
1338
  if (!startCheckpoint) {
1300
- throw new Error(`Local checkpoint ${startCheckpointKey} not found`);
1339
+ await restartJumpStoryFromHead(targetStory, currentStory);
1340
+ return true;
1301
1341
  }
1302
1342
  const restored = await executeScenarioCommand({
1303
1343
  subCommand: "restoreCheckpoint",
@@ -1440,6 +1480,46 @@ var debugSessionController = {
1440
1480
  throw error;
1441
1481
  }
1442
1482
  });
1483
+ },
1484
+ async replaceStory(options) {
1485
+ debugState.restoring = true;
1486
+ debugState.lastError = null;
1487
+ try {
1488
+ const outcome = await enqueueDebugOperation(
1489
+ async () => executeScenarioCommand({
1490
+ subCommand: "replaceStoryData",
1491
+ name: options.story,
1492
+ content: options.content
1493
+ })
1494
+ );
1495
+ if (outcome.plan.mode === "invalidate-only") {
1496
+ pruneLocalStoryCheckpoints(options.story, /* @__PURE__ */ new Set());
1497
+ }
1498
+ if (outcome.plan.mode === "reposition") {
1499
+ if (!outcome.plan.boundary) {
1500
+ throw new Error("Story replace outcome is missing an executable boundary");
1501
+ }
1502
+ const seekMode = options.seekMode ?? "warp";
1503
+ const replaceTargetMarkerId = options.targetMarkerId ?? outcome.plan.targetMarkerId;
1504
+ await executeRestartBoundary(outcome.plan.boundary, options.story);
1505
+ if (replaceTargetMarkerId && !(outcome.plan.boundary.kind === "checkpoint" && outcome.plan.boundary.checkpointKey === replaceTargetMarkerId)) {
1506
+ if (seekMode === "warp") {
1507
+ await runWarp({
1508
+ markerId: replaceTargetMarkerId,
1509
+ boundary: "after"
1510
+ });
1511
+ } else if (seekMode === "fast-forward") {
1512
+ await runFastForwardToMarker(replaceTargetMarkerId);
1513
+ }
1514
+ }
1515
+ }
1516
+ return outcome;
1517
+ } catch (error) {
1518
+ debugState.lastError = toErrorMessage(error);
1519
+ throw error;
1520
+ } finally {
1521
+ debugState.restoring = false;
1522
+ }
1443
1523
  }
1444
1524
  };
1445
1525
  function registerAppStateAdapter(adapter) {
@@ -1544,7 +1624,8 @@ function useDebugSession() {
1544
1624
  stopDebugSession,
1545
1625
  restoreCheckpoint: debugSessionController.restoreCheckpoint,
1546
1626
  warp: debugSessionController.warp,
1547
- switchRoute: debugSessionController.switchRoute
1627
+ switchRoute: debugSessionController.switchRoute,
1628
+ replaceStory: debugSessionController.replaceStory
1548
1629
  };
1549
1630
  }
1550
1631
 
@@ -1634,6 +1715,9 @@ function isJumpRequestMessage(message) {
1634
1715
  function isRouteRequestMessage(message) {
1635
1716
  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));
1636
1717
  }
1718
+ function isStoryReplaceMessage(message) {
1719
+ return message.type === "story:replace" && typeof message.sessionId === "string" && typeof message.requestId === "number" && typeof message.story === "string" && typeof message.content === "string" && (message.seekMode === void 0 || message.seekMode === "off" || message.seekMode === "fast-forward" || message.seekMode === "warp") && (message.targetMarkerId === void 0 || typeof message.targetMarkerId === "string");
1720
+ }
1637
1721
  function isUiReplaceMessage(message) {
1638
1722
  return message.type === "ui:replace" && typeof message.sessionId === "string";
1639
1723
  }
@@ -1720,6 +1804,35 @@ function handleRuntimeDebugRequest(socket, sessionId, message) {
1720
1804
  });
1721
1805
  return;
1722
1806
  }
1807
+ if (isStoryReplaceMessage(message)) {
1808
+ setRuntimeDebugVariableEmissionSuspended(true);
1809
+ void controller.replaceStory({
1810
+ story: message.story,
1811
+ content: message.content,
1812
+ seekMode: message.seekMode,
1813
+ targetMarkerId: message.targetMarkerId
1814
+ }).then(async () => {
1815
+ sendRuntimeDebugMessage(socket, {
1816
+ type: "story:replace:done",
1817
+ sessionId,
1818
+ requestId: message.requestId,
1819
+ story: message.story
1820
+ });
1821
+ await syncRuntimeDebugVariablesSnapshotSafe("story-replace-done");
1822
+ }).catch(async (error) => {
1823
+ sendRuntimeDebugMessage(socket, {
1824
+ type: "story:replace:error",
1825
+ sessionId,
1826
+ requestId: message.requestId,
1827
+ story: message.story,
1828
+ error: error instanceof Error ? error.message : String(error)
1829
+ });
1830
+ await syncRuntimeDebugVariablesSnapshotSafe("story-replace-error");
1831
+ }).finally(() => {
1832
+ setRuntimeDebugVariableEmissionSuspended(false);
1833
+ });
1834
+ return;
1835
+ }
1723
1836
  setRuntimeDebugVariableEmissionSuspended(true);
1724
1837
  void controller.switchRoute(message.page, message.params).then(async () => {
1725
1838
  sendRuntimeDebugMessage(socket, {
@@ -1857,6 +1970,10 @@ async function startRuntimeDebugSession() {
1857
1970
  handleRuntimeDebugRequest(socket, sessionId, message);
1858
1971
  return;
1859
1972
  }
1973
+ if (isStoryReplaceMessage(message)) {
1974
+ handleRuntimeDebugRequest(socket, sessionId, message);
1975
+ return;
1976
+ }
1860
1977
  if (isUiReplaceMessage(message)) {
1861
1978
  handleRuntimeDebugRequest(socket, sessionId, message);
1862
1979
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momoyu-ink/kit",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "Software Development Kit of Moyu Visual Novel Engine",
5
5
  "main": "dist/lib.cjs",
6
6
  "module": "dist/lib.mjs",