@rohal12/spindle 0.39.1 → 0.40.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/pkg/format.js +1 -1
- package/package.json +1 -1
- package/src/index.tsx +4 -1
- package/src/store.ts +42 -0
- package/src/story-api.ts +21 -6
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { render } from 'preact';
|
|
2
2
|
import { App } from './components/App';
|
|
3
3
|
import { parseStoryData } from './parser';
|
|
4
|
-
import { useStoryStore, fireStoryInit } from './store';
|
|
4
|
+
import { useStoryStore, fireStoryInit, enterRuntimePhase } from './store';
|
|
5
5
|
import { installStoryAPI, getReadyPromise } from './story-api';
|
|
6
6
|
import { resetIdCounters } from './action-registry';
|
|
7
7
|
import { executeStoryInit } from './story-init';
|
|
@@ -99,6 +99,9 @@ function boot() {
|
|
|
99
99
|
|
|
100
100
|
useStoryStore.getState().init(storyData, defaults);
|
|
101
101
|
|
|
102
|
+
// Enter runtime phase — handlers registered from here on are cleaned on restart
|
|
103
|
+
enterRuntimePhase();
|
|
104
|
+
|
|
102
105
|
// Execute StoryInit passage if it exists
|
|
103
106
|
executeStoryInit();
|
|
104
107
|
|
package/src/store.ts
CHANGED
|
@@ -177,6 +177,41 @@ function resetModuleState(base: Record<string, unknown>): void {
|
|
|
177
177
|
serializedHistory = [];
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Runtime handler cleanup (auto-unsub on restart)
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
let runtimeUnsubs: Array<() => void> = [];
|
|
185
|
+
let inRuntimePhase = false;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Track an unsubscribe function for automatic cleanup on restart.
|
|
189
|
+
* No-op if called during the startup phase (before enterRuntimePhase).
|
|
190
|
+
*/
|
|
191
|
+
export function trackRuntimeUnsub(unsub: () => void): void {
|
|
192
|
+
if (inRuntimePhase) {
|
|
193
|
+
runtimeUnsubs.push(unsub);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Mark the start of the runtime phase. Called before executeStoryInit(). */
|
|
198
|
+
export function enterRuntimePhase(): void {
|
|
199
|
+
inRuntimePhase = true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Call all tracked unsubs and reset the runtime phase. */
|
|
203
|
+
function cleanupRuntimeHandlers(): void {
|
|
204
|
+
for (const unsub of runtimeUnsubs) unsub();
|
|
205
|
+
runtimeUnsubs = [];
|
|
206
|
+
inRuntimePhase = false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Test-only: reset runtime phase state between tests. */
|
|
210
|
+
export function _resetRuntimePhase(): void {
|
|
211
|
+
runtimeUnsubs = [];
|
|
212
|
+
inRuntimePhase = false;
|
|
213
|
+
}
|
|
214
|
+
|
|
180
215
|
// ---------------------------------------------------------------------------
|
|
181
216
|
// storyinit callbacks (direct invocation — avoids Zustand subscription issues)
|
|
182
217
|
// ---------------------------------------------------------------------------
|
|
@@ -526,6 +561,9 @@ export const useStoryStore = create<StoryState>()(
|
|
|
526
561
|
|
|
527
562
|
const keepDeferred = get().renderDeferred;
|
|
528
563
|
|
|
564
|
+
// Clean up all runtime-phase handlers (after beforerestart has fired)
|
|
565
|
+
cleanupRuntimeHandlers();
|
|
566
|
+
|
|
529
567
|
resetPRNG();
|
|
530
568
|
resetTriggers();
|
|
531
569
|
const initialVars = deepClone(variableDefaults);
|
|
@@ -550,6 +588,10 @@ export const useStoryStore = create<StoryState>()(
|
|
|
550
588
|
});
|
|
551
589
|
|
|
552
590
|
lastNavigationVars = get().variables;
|
|
591
|
+
|
|
592
|
+
// Re-enter runtime phase before StoryInit so new handlers are tracked
|
|
593
|
+
enterRuntimePhase();
|
|
594
|
+
|
|
553
595
|
executeStoryInit();
|
|
554
596
|
clearSession(storyData.ifid);
|
|
555
597
|
fireStoryInit();
|
package/src/story-api.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useStoryStore,
|
|
3
|
+
onStoryInit,
|
|
4
|
+
onBeforeRestart,
|
|
5
|
+
trackRuntimeUnsub,
|
|
6
|
+
} from './store';
|
|
2
7
|
import type { Passage } from './parser';
|
|
3
8
|
import { settings } from './settings';
|
|
4
9
|
import type {
|
|
@@ -371,30 +376,38 @@ function createStoryAPI(): StoryAPI {
|
|
|
371
376
|
on(event: string, callback: (...args: any[]) => void): () => void {
|
|
372
377
|
if (event === 'navigate') {
|
|
373
378
|
let prev = useStoryStore.getState().currentPassage;
|
|
374
|
-
|
|
379
|
+
const unsub = useStoryStore.subscribe((state) => {
|
|
375
380
|
if (state.currentPassage !== prev) {
|
|
376
381
|
const from = prev;
|
|
377
382
|
prev = state.currentPassage;
|
|
378
383
|
(callback as NavigateCallback)(state.currentPassage, from);
|
|
379
384
|
}
|
|
380
385
|
});
|
|
386
|
+
trackRuntimeUnsub(unsub);
|
|
387
|
+
return unsub;
|
|
381
388
|
}
|
|
382
389
|
|
|
383
390
|
if (event === 'beforerestart') {
|
|
384
|
-
|
|
391
|
+
const unsub = onBeforeRestart(callback as BeforeRestartCallback);
|
|
392
|
+
trackRuntimeUnsub(unsub);
|
|
393
|
+
return unsub;
|
|
385
394
|
}
|
|
386
395
|
|
|
387
396
|
if (event === 'storyinit') {
|
|
388
|
-
|
|
397
|
+
const unsub = onStoryInit(callback as StoryInitCallback);
|
|
398
|
+
trackRuntimeUnsub(unsub);
|
|
399
|
+
return unsub;
|
|
389
400
|
}
|
|
390
401
|
|
|
391
402
|
if (event === 'actionsChanged') {
|
|
392
|
-
|
|
403
|
+
const unsub = onActionsChanged(callback as ActionsChangedCallback);
|
|
404
|
+
trackRuntimeUnsub(unsub);
|
|
405
|
+
return unsub;
|
|
393
406
|
}
|
|
394
407
|
|
|
395
408
|
if (event === 'variableChanged') {
|
|
396
409
|
let prevVars = { ...useStoryStore.getState().variables };
|
|
397
|
-
|
|
410
|
+
const unsub = useStoryStore.subscribe((state) => {
|
|
398
411
|
const changed: Record<string, { from: unknown; to: unknown }> = {};
|
|
399
412
|
let hasChanges = false;
|
|
400
413
|
const allKeys = new Set([
|
|
@@ -412,6 +425,8 @@ function createStoryAPI(): StoryAPI {
|
|
|
412
425
|
(callback as VariableChangedCallback)(changed);
|
|
413
426
|
}
|
|
414
427
|
});
|
|
428
|
+
trackRuntimeUnsub(unsub);
|
|
429
|
+
return unsub;
|
|
415
430
|
}
|
|
416
431
|
|
|
417
432
|
throw new Error(`spindle: Unknown event "${event}".`);
|