sia-reactor 0.0.20 → 0.0.21

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/plugins.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  reactive
3
- } from "./chunk-VPR5SP3E.js";
3
+ } from "./chunk-2WBPGSRL.js";
4
4
  import {
5
5
  clamp,
6
6
  guardAllMethods,
@@ -246,9 +246,10 @@ var PERSIST_PLUGIN_BUILD = { disabled: false, key: "REACTOR_STORE", throttle: 25
246
246
  // src/ts/plugins/timeTravel.ts
247
247
  var TimeTravelPlugin = class extends BaseReactorPlugin {
248
248
  static plugName = "timeTravel";
249
+ lastTimestamp = 0;
249
250
  playbackTimeoutId = -1;
250
251
  constructor(config, rtr) {
251
- super({ ...TIME_TRAVEL_PLUGIN_BUILD, ...config }, rtr, { history: [], initialState: null, currentFrame: 0, paused: true });
252
+ super({ ...TIME_TRAVEL_PLUGIN_BUILD, ...config }, rtr, { initialState: null, history: [], currentFrame: 0, paused: true });
252
253
  }
253
254
  // ===========================================================================
254
255
  // THE FOUNDATION & WIRETAP (Passive Recording)
@@ -256,6 +257,7 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
256
257
  wire() {
257
258
  this.rtr.config.referenceTracking = this.rtr.config.smartCloning = this.rtr.config.eventTimeStamps = true;
258
259
  if (!this.state.history.length || this.state.initialState == null) this.state.initialState = this.rtr.snapshot();
260
+ this.lastTimestamp = performance.now();
259
261
  this.state.set("currentFrame", (v = 0) => clamp(0, v, this.state.history.length), { signal: this.signal, immediate: true });
260
262
  this.config.on("paths", this.handlePathsState, { signal: this.signal, immediate: true });
261
263
  !this.state.paused && this.play();
@@ -269,8 +271,9 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
269
271
  if (!this.state.paused) return;
270
272
  if (this.state.currentFrame < this.state.history.length) this.state.history = this.state.history.slice(0, this.state.currentFrame);
271
273
  if (this.state.history.length >= this.config.maxHistoryLength) this.state.history = this.state.history.slice(1);
272
- this.state.history.push({ timestamp: e.timestamp ?? performance.now(), path: e.target.path, type: e.staticType, value: this.rtr.snapshot(false, e.target.value), oldValue: this.rtr.snapshot(false, e.target.oldValue), hadKey: e.target.hadKey, rejected: e.rejected });
274
+ this.state.history.push({ path: e.target.path, value: this.rtr.snapshot(false, e.target.value), oldValue: this.rtr.snapshot(false, e.target.oldValue), type: e.staticType, rejected: e.rejected, timedelta: e.timestamp - this.lastTimestamp, hadKey: e.target.hadKey });
273
275
  this.state.currentFrame = this.state.history.length;
276
+ this.lastTimestamp = e.timestamp;
274
277
  }
275
278
  /** Clears timeline history and resets playhead/genesis to the current reactor state. */
276
279
  clear() {
@@ -278,6 +281,7 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
278
281
  this.playbackTimeoutId = -1;
279
282
  this.state.history.length = this.state.currentFrame = 0;
280
283
  this.state.initialState = this.rtr.snapshot();
284
+ this.lastTimestamp = performance.now();
281
285
  }
282
286
  // ===========================================================================
283
287
  // THE TIME MACHINE (Manual Controls)
@@ -313,9 +317,9 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
313
317
  async automove(forward = true) {
314
318
  this.state.paused = false;
315
319
  while ((forward ? this.state.currentFrame < this.state.history.length : this.state.currentFrame > 0) && !this.state.paused) {
316
- const currIndex = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[currIndex], nextE = this.state.history[forward ? currIndex + 1 : currIndex - 1], delay = nextE && e ? Math.abs(nextE.timestamp - e.timestamp) : 0;
320
+ const currIndex = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[forward ? currIndex + 1 : currIndex - 1];
317
321
  this.jumpTo(this.state.currentFrame + (forward ? 1 : -1), true);
318
- if (delay > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout(() => res(0), clamp(0, delay, this.config.maxPlaybackDelay), this.signal));
322
+ if (e?.timedelta > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout(() => res(0), Math.min(e.timedelta, this.config.maxPlaybackDelay), this.signal));
319
323
  }
320
324
  this.state.paused = true;
321
325
  }
@@ -329,21 +333,24 @@ var TimeTravelPlugin = class extends BaseReactorPlugin {
329
333
  // TELEMETRY & I/O (Session Import/Export)
330
334
  // ===========================================================================
331
335
  /** Exports the current session as a JSON string. */
332
- export() {
333
- return JSON.stringify(this.state);
336
+ export(replacer, space) {
337
+ try {
338
+ return JSON.stringify(this.state, replacer, space);
339
+ } catch (e) {
340
+ return this.rtr.log(`[Reactor ${this.name} Plug] Failed to export session`), "";
341
+ }
334
342
  }
335
343
  /** Imports a session from a JSON string, allowing you to replay or analyze past states. */
336
344
  import(json) {
337
345
  try {
338
346
  setAny(this.state, "*", JSON.parse(json));
347
+ this.lastTimestamp = performance.now();
339
348
  const resume = !this.state.paused, target = this.state.currentFrame;
340
349
  this.state.paused = false;
341
- setAny(this.rtr.core, "*", deepClone(this.state.initialState, this.rtr.config));
342
- this.rtr.tick();
343
- this.state.currentFrame = 0;
344
- this.jumpTo(target), resume && this.play();
350
+ setAny(this.rtr.core, "*", deepClone(this.state.initialState, this.rtr.config)), this.rtr.tick();
351
+ this.state.currentFrame = 0, this.jumpTo(target), resume && this.play();
345
352
  } catch (e) {
346
- this.rtr.log(`[Reactor ${this.name} Plug] Failed to load session`, "error");
353
+ this.rtr.log(`[Reactor ${this.name} Plug] Failed to load session`);
347
354
  }
348
355
  }
349
356
  };
@@ -154,7 +154,7 @@
154
154
 
155
155
  .tt-io {
156
156
  min-height: 84px;
157
- max-height: calc(100vh - 350px);
157
+ max-height: calc((100vh - 318px) / 2);
158
158
  resize: vertical;
159
159
  border: 1px solid var(--sia-tt-line);
160
160
  background: var(--sia-tt-input-bg);
package/dist/super.d.ts CHANGED
@@ -769,13 +769,13 @@ declare class Reactor<T extends object> {
769
769
  protected listeners?: Map<WildPaths<T>, Array<ListenerRecord<T>>>;
770
770
  /**
771
771
  * Creates a new Reactor instance.
772
- * @param object Initial state object.
772
+ * @param target Initial state target.
773
773
  * @param build Reactor bootstrap/build configuration.
774
774
  * @example
775
775
  * const rtr = new Reactor({ count: 0 });
776
776
  */
777
- constructor(object?: T, build?: ReactorBuild<T>);
778
- proxied<O extends object>(obj: O, rejectable?: boolean, indiffable?: boolean, parent?: object, key?: string, path?: string): O;
777
+ constructor(target?: T, build?: ReactorBuild<T>);
778
+ proxied<O extends object>(target: O, rejectable?: boolean, indiffable?: boolean, parent?: object, key?: string, path?: string): O;
779
779
  trace(target: object, path: string, paths?: string[], seen?: WeakSet<object>): Paths<T>[];
780
780
  protected link(target: any, parent: object, key: string, typecheck?: boolean, es?: (object | string)[]): boolean;
781
781
  protected unlink(target: any, parent: object, key: string): void;
@@ -815,7 +815,7 @@ declare class Reactor<T extends object> {
815
815
  getDepth(path: string, depth?: number): number;
816
816
  protected getContext<P extends WildPaths<T>>(path: P): Target<T, P>;
817
817
  protected bindSignal<Cb>(cord: GetterRecord<T> | SetterRecord<T> | DeleterRecord<T> | WatcherRecord<T> | ListenerRecord<T>, sig?: AbortSignal): Cb;
818
- protected cloned(obj: any, raw: boolean, seen?: WeakMap<WeakKey, any>): any;
818
+ protected cloned<O>(target: O, raw: boolean, seen?: WeakMap<WeakKey, any>): O;
819
819
  protected syncAdd<P extends WildPaths<T>>(key: "get" | "set" | "delete" | "watch", path: P, cb: any, opts: SyncOptions | undefined, onImmediate?: (immediate: boolean | "auto") => void): () => boolean | undefined;
820
820
  protected syncDrop<P extends WildPaths<T>>(store: Map<WildPaths<T>, any[]> | undefined, path: P, cb: any): boolean | undefined;
821
821
  /**
@@ -931,7 +931,8 @@ declare class Reactor<T extends object> {
931
931
  * @example
932
932
  * const snap = rtr.snapshot(false, rtr.core.history.past);
933
933
  */
934
- snapshot(raw?: boolean, branch?: T): T;
934
+ snapshot(raw?: boolean): T;
935
+ snapshot<B>(raw?: boolean, branch?: B): B;
935
936
  /**
936
937
  * Cascades object updates into direct child paths.
937
938
  * @param payload Event or payload source.
@@ -1390,20 +1391,20 @@ declare const PERSIST_PLUGIN_BUILD: Partial<PersistConfig<any>>;
1390
1391
 
1391
1392
  /** The DNA of a specific moment in time, Records the 'Desire' (Intent) or the 'Fact' (State). */
1392
1393
  interface HistoryEntry {
1393
- /** Was it a 'set' or a 'delete' surgery? */
1394
- type: REvent<any, any>["staticType"];
1395
1394
  /** The surgical address in the Reactor */
1396
1395
  path: string;
1397
1396
  /** The data payload at that moment */
1398
1397
  value: any;
1399
1398
  /** The "Undo" antidote (Previous value), if applicable */
1400
1399
  oldValue: any;
1401
- /** Did the key for the value exist on its parent object? */
1402
- hadKey: boolean;
1400
+ /** Was it a 'set' or a 'delete' surgery? */
1401
+ type: REvent<any, any>["staticType"];
1403
1402
  /** Did the Power Line disapprove?; why? */
1404
1403
  rejected: string;
1405
1404
  /** For chronological re-enactment */
1406
- timestamp: number;
1405
+ timedelta: number;
1406
+ /** Did the key for the value exist on its parent object? */
1407
+ hadKey: boolean;
1407
1408
  }
1408
1409
  interface TimeTravelConfig$1<T extends object = any> {
1409
1410
  /** Specific paths only, no "*"; instead don't pass anything */
@@ -1414,10 +1415,10 @@ interface TimeTravelConfig$1<T extends object = any> {
1414
1415
  maxPlaybackDelay: number;
1415
1416
  }
1416
1417
  interface TimeTravelState {
1417
- /** The "Timeline" of mutations (Chronological Log) */
1418
- history: HistoryEntry[];
1419
1418
  /** The "Genesis" snapshot (Raw Data) */
1420
1419
  initialState: any;
1420
+ /** The "Timeline" of mutations (Chronological Log) */
1421
+ history: HistoryEntry[];
1421
1422
  /** The manual playhead (Index in the Timeline) */
1422
1423
  currentFrame: number;
1423
1424
  /** Whether playback is currently paused (Automatic Replay) */
@@ -1429,6 +1430,7 @@ interface TimeTravelState {
1429
1430
  */
1430
1431
  declare class TimeTravelPlugin<T extends object = any> extends BaseReactorPlugin<T, TimeTravelConfig$1<T>, TimeTravelState> {
1431
1432
  static readonly plugName = "timeTravel";
1433
+ protected lastTimestamp: number;
1432
1434
  protected playbackTimeoutId: number;
1433
1435
  constructor(config?: Partial<TimeTravelConfig$1<T>>, rtr?: Reactor<T>);
1434
1436
  wire(): void;
@@ -1454,7 +1456,7 @@ declare class TimeTravelPlugin<T extends object = any> extends BaseReactorPlugin
1454
1456
  /** Pauses the live VCR playback. */
1455
1457
  pause: () => void;
1456
1458
  /** Exports the current session as a JSON string. */
1457
- export(): string;
1459
+ export(replacer?: ((this: any, key: string, value: any) => any) | (number | string)[] | null, space?: string | number): string;
1458
1460
  /** Imports a session from a JSON string, allowing you to replay or analyze past states. */
1459
1461
  import(json: string): void;
1460
1462
  }
@@ -363,27 +363,27 @@ var sia = (() => {
363
363
  listeners;
364
364
  /**
365
365
  * Creates a new Reactor instance.
366
- * @param object Initial state object.
366
+ * @param target Initial state target.
367
367
  * @param build Reactor bootstrap/build configuration.
368
368
  * @example
369
369
  * const rtr = new Reactor({ count: 0 });
370
370
  */
371
- constructor(object = {}, build) {
371
+ constructor(target = {}, build) {
372
372
  this[INERTIA] = true;
373
373
  this.config = { crossRealms: false, smartCloning: false, eventBubbling: true, lineageTracing: false, preserveContext: false, equalityFunction: Object.is, batchingFunction: RTR_BATCH, ...build };
374
- this.core = this.proxied(object);
374
+ this.core = this.proxied(target);
375
375
  if (build) this.canLog = !!build.debug;
376
376
  }
377
- proxied(obj, rejectable = false, indiffable = false, parent, key, path) {
378
- if (!obj || "object" !== typeof obj) return obj;
379
- obj = obj[RAW] || obj;
380
- if (this.config.referenceTracking && parent && key && !this.link(obj, parent, key, false)) return obj;
381
- const cached = this.proxyCache.get(obj);
377
+ proxied(target, rejectable = false, indiffable = false, parent, key, path) {
378
+ if (!target || "object" !== typeof target) return target;
379
+ target = target[RAW] || target;
380
+ if (this.config.referenceTracking && parent && key && !this.link(target, parent, key, false)) return target;
381
+ const cached = this.proxyCache.get(target);
382
382
  if (cached) return cached;
383
- if (obj[INERTIA] || !canHandle(obj, this.config, false)) return obj;
384
- rejectable ||= obj[REJECTABLE];
385
- indiffable ||= obj[INDIFFABLE];
386
- const proxy = new Proxy(obj, {
383
+ if (target[INERTIA] || !canHandle(target, this.config, false)) return target;
384
+ rejectable ||= target[REJECTABLE];
385
+ indiffable ||= target[INDIFFABLE];
386
+ const proxy = new Proxy(target, {
387
387
  // Robust Proxy handler
388
388
  get: (object, key2, receiver) => {
389
389
  if (key2 === RAW) return this.log(`\u{1F440} [Reactor \`get\` Trap] Peeked at ${object}`), object;
@@ -396,10 +396,10 @@ var sia = (() => {
396
396
  for (let i = 0, len = this.config.lineageTracing ? paths.length : 1; i < len; i++) {
397
397
  const currPath = this.config.lineageTracing ? paths[i] : fullPath, cords = this.getters.get(currPath);
398
398
  if (!cords && !wildcords) continue;
399
- const target = { path: currPath, value, key: keyStr, hadKey: true, object: receiver }, payload = { type: "get", target, currentTarget: target, root: this.core, rejectable };
399
+ const target2 = { path: currPath, value, key: keyStr, hadKey: true, object: receiver }, payload = { type: "get", target: target2, currentTarget: target2, root: this.core, rejectable };
400
400
  if (cords) value = this.mediate(currPath, payload, "get", cords);
401
401
  if (!wildcords) continue;
402
- target.value = value;
402
+ target2.value = value;
403
403
  value = this.mediate("*", payload, "get", wildcords);
404
404
  }
405
405
  }
@@ -421,13 +421,13 @@ var sia = (() => {
421
421
  for (let i = 0; i < loopLen; i++) {
422
422
  const currPath = this.config.lineageTracing ? paths[i] : fullPath, cords = this.setters.get(currPath);
423
423
  if (!cords && !wildcords) continue;
424
- const target = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver }, payload = { type: "set", target, currentTarget: target, root: this.core, terminated, rejectable };
424
+ const target2 = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver }, payload = { type: "set", target: target2, currentTarget: target2, root: this.core, terminated, rejectable };
425
425
  if (cords) {
426
426
  const result2 = this.mediate(currPath, payload, "set", cords);
427
427
  if (!(terminated ||= payload.terminated)) value = result2;
428
428
  }
429
429
  if (!wildcords) continue;
430
- target.value = value;
430
+ target2.value = value;
431
431
  const result = this.mediate("*", payload, "set", wildcords);
432
432
  if (!(terminated ||= payload.terminated)) value = result;
433
433
  }
@@ -438,8 +438,8 @@ var sia = (() => {
438
438
  if (this.config.referenceTracking && !unchanged) this.config.smartCloning && this.stamp(object), this.unlink(safeOldValue, object, keyStr), this.link(safeValue, object, keyStr);
439
439
  if (this.watchers || this.listeners)
440
440
  for (let i = 0; i < loopLen; i++) {
441
- const currPath = this.config.lineageTracing ? paths[i] : fullPath, target = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver };
442
- this.notify(currPath, { type: "set", target, currentTarget: target, root: this.core, terminated, rejectable });
441
+ const currPath = this.config.lineageTracing ? paths[i] : fullPath, target2 = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver };
442
+ this.notify(currPath, { type: "set", target: target2, currentTarget: target2, root: this.core, terminated, rejectable });
443
443
  }
444
444
  return true;
445
445
  },
@@ -453,7 +453,7 @@ var sia = (() => {
453
453
  for (let i = 0; i < loopLen; i++) {
454
454
  const currPath = this.config.lineageTracing ? paths[i] : fullPath, cords = this.deleters.get(currPath);
455
455
  if (!cords && !wildcords) continue;
456
- const target = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver }, payload = { type: "delete", target, currentTarget: target, root: this.core, rejectable };
456
+ const target2 = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver }, payload = { type: "delete", target: target2, currentTarget: target2, root: this.core, rejectable };
457
457
  if (cords) {
458
458
  const result2 = this.mediate(currPath, payload, "delete", cords);
459
459
  if (!(terminated ||= payload.terminated)) value = result2;
@@ -469,8 +469,8 @@ var sia = (() => {
469
469
  if (this.config.referenceTracking) this.config.smartCloning && this.stamp(object), this.unlink(oldValue?.[RAW] || oldValue, object, keyStr);
470
470
  if (this.watchers || this.listeners)
471
471
  for (let i = 0; i < loopLen; i++) {
472
- const currPath = this.config.lineageTracing ? paths[i] : fullPath, target = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver };
473
- this.notify(currPath, { type: "delete", target, currentTarget: target, root: this.core, rejectable });
472
+ const currPath = this.config.lineageTracing ? paths[i] : fullPath, target2 = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver };
473
+ this.notify(currPath, { type: "delete", target: target2, currentTarget: target2, root: this.core, rejectable });
474
474
  }
475
475
  return true;
476
476
  },
@@ -496,7 +496,7 @@ var sia = (() => {
496
496
  return ownKeys;
497
497
  }
498
498
  });
499
- return this.proxyCache.set(obj, proxy), proxy;
499
+ return this.proxyCache.set(target, proxy), proxy;
500
500
  }
501
501
  trace(target, path, paths = [], seen = /* @__PURE__ */ new WeakSet()) {
502
502
  if (Object.is(target, this.core[RAW] || this.core)) return paths.push(path), paths;
@@ -649,15 +649,14 @@ var sia = (() => {
649
649
  if (sig) sig.aborted ? cord.clup() : sig.addEventListener("abort", cord.clup, { once: true });
650
650
  return cord.sclup = !sig || sig.aborted ? NOOP : () => sig.removeEventListener("abort", cord.clup), cord.clup;
651
651
  }
652
- cloned(obj, raw, seen = /* @__PURE__ */ new WeakMap()) {
653
- if (!obj || "object" !== typeof obj) return obj;
654
- obj = obj[RAW] || obj;
655
- const cloned = seen.get(obj);
652
+ cloned(target, raw, seen = /* @__PURE__ */ new WeakMap()) {
653
+ if (!target || "object" !== typeof target) return target;
654
+ const obj = target[RAW] || target, cloned = seen.get(obj);
656
655
  if (cloned) return cloned;
657
656
  if (!canHandle(obj, this.config, false)) return obj;
658
657
  const version = obj[VERSION] || 0, cached = !raw && this.config.smartCloning && (this.snapCache ??= /* @__PURE__ */ new WeakMap()).get(obj);
659
658
  if (cached && obj[SSVERSION] === version) return cached;
660
- const clone = !raw ? this.config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {} : obj[RAW] || obj;
659
+ const clone = !raw ? this.config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {} : obj;
661
660
  seen.set(obj, clone);
662
661
  const keys2 = this.config.preserveContext ? Reflect.ownKeys(obj) : Object.keys(obj);
663
662
  for (let i = 0, len = keys2.length; i < len; i++) clone[keys2[i]] = this.cloned(obj[keys2[i]], raw, seen);
@@ -841,19 +840,8 @@ var sia = (() => {
841
840
  for (let i = 0, len = cords.length; i < len; i++) if (Object.is(cords[i].cb, callback) && cords[i].capture === capture) return cords[i].sclup(), cords.splice((len--, i--), 1), !cords.length && this.listeners.delete(path), true;
842
841
  return false;
843
842
  }
844
- /**
845
- * Creates a snapshot; possibly clone of state (or a state branch).
846
- * You could alternatively use or serialize your proxied state "as is" except the environment demands no proxies or new references.
847
- * @param raw Use raw (deep unproxied & uncloned) version of branch.
848
- * @param branch Branch to clone.
849
- * @returns Snapshot deep or smart (structurally shared) clone.
850
- * @example
851
- * const snap = rtr.snapshot();
852
- * @example
853
- * const snap = rtr.snapshot(false, rtr.core.history.past);
854
- */
855
- snapshot(raw = !this.config.smartCloning, branch = this.core) {
856
- return this.cloned(branch, raw);
843
+ snapshot(raw = !this.config.smartCloning, branch) {
844
+ return this.cloned(arguments.length < 2 ? this.core : branch, raw);
857
845
  }
858
846
  /**
859
847
  * Cascades object updates into direct child paths.
@@ -1379,9 +1367,10 @@ var sia = (() => {
1379
1367
  // src/ts/plugins/timeTravel.ts
1380
1368
  var TimeTravelPlugin = class extends BaseReactorPlugin {
1381
1369
  static plugName = "timeTravel";
1370
+ lastTimestamp = 0;
1382
1371
  playbackTimeoutId = -1;
1383
1372
  constructor(config, rtr) {
1384
- super({ ...TIME_TRAVEL_PLUGIN_BUILD, ...config }, rtr, { history: [], initialState: null, currentFrame: 0, paused: true });
1373
+ super({ ...TIME_TRAVEL_PLUGIN_BUILD, ...config }, rtr, { initialState: null, history: [], currentFrame: 0, paused: true });
1385
1374
  }
1386
1375
  // ===========================================================================
1387
1376
  // THE FOUNDATION & WIRETAP (Passive Recording)
@@ -1389,6 +1378,7 @@ var sia = (() => {
1389
1378
  wire() {
1390
1379
  this.rtr.config.referenceTracking = this.rtr.config.smartCloning = this.rtr.config.eventTimeStamps = true;
1391
1380
  if (!this.state.history.length || this.state.initialState == null) this.state.initialState = this.rtr.snapshot();
1381
+ this.lastTimestamp = performance.now();
1392
1382
  this.state.set("currentFrame", (v = 0) => clamp(0, v, this.state.history.length), { signal: this.signal, immediate: true });
1393
1383
  this.config.on("paths", this.handlePathsState, { signal: this.signal, immediate: true });
1394
1384
  !this.state.paused && this.play();
@@ -1402,8 +1392,9 @@ var sia = (() => {
1402
1392
  if (!this.state.paused) return;
1403
1393
  if (this.state.currentFrame < this.state.history.length) this.state.history = this.state.history.slice(0, this.state.currentFrame);
1404
1394
  if (this.state.history.length >= this.config.maxHistoryLength) this.state.history = this.state.history.slice(1);
1405
- this.state.history.push({ timestamp: e.timestamp ?? performance.now(), path: e.target.path, type: e.staticType, value: this.rtr.snapshot(false, e.target.value), oldValue: this.rtr.snapshot(false, e.target.oldValue), hadKey: e.target.hadKey, rejected: e.rejected });
1395
+ this.state.history.push({ path: e.target.path, value: this.rtr.snapshot(false, e.target.value), oldValue: this.rtr.snapshot(false, e.target.oldValue), type: e.staticType, rejected: e.rejected, timedelta: e.timestamp - this.lastTimestamp, hadKey: e.target.hadKey });
1406
1396
  this.state.currentFrame = this.state.history.length;
1397
+ this.lastTimestamp = e.timestamp;
1407
1398
  }
1408
1399
  /** Clears timeline history and resets playhead/genesis to the current reactor state. */
1409
1400
  clear() {
@@ -1411,6 +1402,7 @@ var sia = (() => {
1411
1402
  this.playbackTimeoutId = -1;
1412
1403
  this.state.history.length = this.state.currentFrame = 0;
1413
1404
  this.state.initialState = this.rtr.snapshot();
1405
+ this.lastTimestamp = performance.now();
1414
1406
  }
1415
1407
  // ===========================================================================
1416
1408
  // THE TIME MACHINE (Manual Controls)
@@ -1446,9 +1438,9 @@ var sia = (() => {
1446
1438
  async automove(forward = true) {
1447
1439
  this.state.paused = false;
1448
1440
  while ((forward ? this.state.currentFrame < this.state.history.length : this.state.currentFrame > 0) && !this.state.paused) {
1449
- const currIndex = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[currIndex], nextE = this.state.history[forward ? currIndex + 1 : currIndex - 1], delay = nextE && e ? Math.abs(nextE.timestamp - e.timestamp) : 0;
1441
+ const currIndex = forward ? this.state.currentFrame : this.state.currentFrame - 1, e = this.state.history[forward ? currIndex + 1 : currIndex - 1];
1450
1442
  this.jumpTo(this.state.currentFrame + (forward ? 1 : -1), true);
1451
- if (delay > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout2(() => res(0), clamp(0, delay, this.config.maxPlaybackDelay), this.signal));
1443
+ if (e?.timedelta > 0) await new Promise((res) => this.playbackTimeoutId = setTimeout2(() => res(0), Math.min(e.timedelta, this.config.maxPlaybackDelay), this.signal));
1452
1444
  }
1453
1445
  this.state.paused = true;
1454
1446
  }
@@ -1462,21 +1454,24 @@ var sia = (() => {
1462
1454
  // TELEMETRY & I/O (Session Import/Export)
1463
1455
  // ===========================================================================
1464
1456
  /** Exports the current session as a JSON string. */
1465
- export() {
1466
- return JSON.stringify(this.state);
1457
+ export(replacer, space) {
1458
+ try {
1459
+ return JSON.stringify(this.state, replacer, space);
1460
+ } catch (e) {
1461
+ return this.rtr.log(`[Reactor ${this.name} Plug] Failed to export session`), "";
1462
+ }
1467
1463
  }
1468
1464
  /** Imports a session from a JSON string, allowing you to replay or analyze past states. */
1469
1465
  import(json) {
1470
1466
  try {
1471
1467
  setAny(this.state, "*", JSON.parse(json));
1468
+ this.lastTimestamp = performance.now();
1472
1469
  const resume = !this.state.paused, target = this.state.currentFrame;
1473
1470
  this.state.paused = false;
1474
- setAny(this.rtr.core, "*", deepClone(this.state.initialState, this.rtr.config));
1475
- this.rtr.tick();
1476
- this.state.currentFrame = 0;
1477
- this.jumpTo(target), resume && this.play();
1471
+ setAny(this.rtr.core, "*", deepClone(this.state.initialState, this.rtr.config)), this.rtr.tick();
1472
+ this.state.currentFrame = 0, this.jumpTo(target), resume && this.play();
1478
1473
  } catch (e) {
1479
- this.rtr.log(`[Reactor ${this.name} Plug] Failed to load session`, "error");
1474
+ this.rtr.log(`[Reactor ${this.name} Plug] Failed to load session`);
1480
1475
  }
1481
1476
  }
1482
1477
  };
@@ -1663,41 +1658,16 @@ var sia = (() => {
1663
1658
  */
1664
1659
  constructor(time, build = {}) {
1665
1660
  this.time = time;
1666
- this.config = reactive({ container: document.body, color: "", startOpen: false, devOnly: true, title: `Time Travel Overlay ${this.index = ++_TimeTravelOverlay.count}`, ...build });
1661
+ this.config = reactive({ title: `Time Travel Overlay ${this.index = ++_TimeTravelOverlay.count}`, ...build });
1667
1662
  this.state.open = !!this.config.startOpen;
1668
- const host = createEl("div", { className: "tt-overlay-host" });
1669
- const toggle = createEl("button", { className: "tt-overlay-toggle", type: "button", onclick: () => this.state.open = !this.state.open });
1670
- const panel = createEl("aside", { className: "tt-overlay", ariaLabel: "time travel overlay" });
1671
- const title = createEl("div", { className: "title" });
1672
- const frame = createEl("span", { className: "muted" });
1673
- const history = createEl("span", { className: "muted" });
1674
- const paused = createEl("span", { className: "muted" });
1675
- const clrHistory = createEl("button", { textContent: `Clear History${formatKeyForDisplay(keys.shortcuts.clrHistory)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clrHistory, false), onclick: () => (this.time.clear(), this.state.import = "") });
1676
- const undo = createEl("button", { textContent: `Undo${formatKeyForDisplay(keys.shortcuts.undo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.undo, false), onclick: this.time.undo });
1677
- const redo = createEl("button", { textContent: `Redo${formatKeyForDisplay(keys.shortcuts.redo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.redo, false), onclick: this.time.redo });
1678
- const genesis = createEl("button", { textContent: `Genesis${formatKeyForDisplay(keys.shortcuts.genesis[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.genesis, false), onclick: () => this.time.jumpTo(0) });
1679
- const playPause = createEl("button", { onclick: () => this.time[this.time.state.paused ? "play" : "pause"](), ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.playPause, false) });
1680
- const rewind = createEl("button", { textContent: `Rewind${formatKeyForDisplay(keys.shortcuts.rewind)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.rewind, false), onclick: this.time.rewind });
1681
- const range = createEl("input", { type: "range", min: "0", max: "0", value: "0", title: "time travel frame", ariaLabel: "time travel frame", oninput: () => this.time.jumpTo(Number(range.value)) });
1682
- const exp = createEl("button", { textContent: `Export${formatKeyForDisplay(keys.shortcuts.export)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.export, false), onclick: () => this.state.import = this.time.export() });
1683
- const imp = createEl("button", { textContent: `Import${formatKeyForDisplay(keys.shortcuts.import)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.import, false), onclick: () => this.state.import.trim().length && this.time.import(this.state.import) });
1684
- const clr = createEl("button", { textContent: `Clear${formatKeyForDisplay(keys.shortcuts.clear)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clear, false), onclick: () => this.state.import = "" });
1685
- const io = createEl("textarea", { className: "tt-io", placeholder: "timeline payload json", oninput: () => this.state.import = io.value });
1686
- const foot = createEl("p", { className: "tt-footnote", textContent: "Want this in your app? " });
1687
- const link = createEl("a", { target: "_blank", rel: "noreferrer noopener", textContent: "sia-reactor", href: "https://www.npmjs.com/package/sia-reactor" });
1688
- const box = createEl("div", { className: "tt-status-box" });
1689
- const status = createEl("div", { className: "tt-status-row" });
1690
- const row1 = createEl("div", { className: "tt-row" });
1691
- const row2 = createEl("div", { className: "tt-row" });
1692
- const row3 = createEl("div", { className: "tt-row" });
1693
- status.append((box.append(frame, history, paused), box), clrHistory);
1694
- panel.append(title, status, (row1.append(undo, redo, genesis), row1), (row2.append(playPause, rewind), row2), range, (row3.append(exp, imp, clr), row3), io, (foot.appendChild(link), foot));
1663
+ const s = this.time.state, host = createEl("div", { className: "tt-overlay-host" }), toggle = createEl("button", { className: "tt-overlay-toggle", type: "button", onclick: () => this.state.open = !this.state.open }), panel = createEl("aside", { className: "tt-overlay", ariaLabel: "time travel overlay" }), title = createEl("div", { className: "title" }), frame = createEl("span", { className: "muted" }), clrHistory = createEl("button", { textContent: `Clear History${formatKeyForDisplay(keys.shortcuts.clrHistory)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clrHistory, false), onclick: () => (this.time.clear(), this.state.import = "") }), undo = createEl("button", { textContent: `Undo${formatKeyForDisplay(keys.shortcuts.undo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.undo, false), onclick: this.time.undo }), redo = createEl("button", { textContent: `Redo${formatKeyForDisplay(keys.shortcuts.redo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.redo, false), onclick: this.time.redo }), genesis = createEl("button", { textContent: `Genesis${formatKeyForDisplay(keys.shortcuts.genesis[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.genesis, false), onclick: () => this.time.jumpTo(0) }), playPause = createEl("button", { onclick: () => this.time[s.paused ? "play" : "pause"](), ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.playPause, false) }), rewind = createEl("button", { textContent: `Rewind${formatKeyForDisplay(keys.shortcuts.rewind)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.rewind, false), onclick: this.time.rewind }), range = createEl("input", { type: "range", min: "0", max: "0", value: "0", title: "time travel frame", ariaLabel: "time travel frame", oninput: () => this.time.jumpTo(Number(range.value)) }), exp = createEl("button", { textContent: `Export${formatKeyForDisplay(keys.shortcuts.export)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.export, false), onclick: () => this.state.import = this.time.export(null, 2) }), imp = createEl("button", { textContent: `Import${formatKeyForDisplay(keys.shortcuts.import)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.import, false), onclick: () => this.state.import.trim().length && this.time.import(this.state.import) }), clr = createEl("button", { textContent: `Clear${formatKeyForDisplay(keys.shortcuts.clear)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clear, false), onclick: () => this.state.import = "" }), payload = createEl("textarea", { className: "tt-io", readOnly: true, placeholder: "current payload json", title: "current payload" }), io = createEl("textarea", { className: "tt-io", placeholder: "timeline payload json", oninput: () => this.state.import = io.value }), foot = createEl("p", { className: "tt-footnote", textContent: "Want this in your app? " }), link = createEl("a", { target: "_blank", rel: "noreferrer noopener", textContent: "sia-reactor", href: "https://www.npmjs.com/package/sia-reactor" }), box = createEl("div", { className: "tt-status-box" }), status = createEl("div", { className: "tt-status-row" }), row1 = createEl("div", { className: "tt-row" }), row2 = createEl("div", { className: "tt-row" }), row3 = createEl("div", { className: "tt-row" });
1664
+ status.append((box.append(frame), box), clrHistory);
1665
+ panel.append(title, status, (row1.append(undo, redo, genesis), row1), (row2.append(playPause, rewind), row2), payload, range, (row3.append(exp, imp, clr), row3), io, (foot.appendChild(link), foot));
1695
1666
  host.append(toggle, panel);
1696
- this.els = { host, toggle, panel, title, frame, history, paused, clrHistory, undo, redo, genesis, playPause, rewind, range, exp, imp, clr, io };
1667
+ this.els = { host, toggle, panel, title, frame, clrHistory, undo, redo, genesis, playPause, rewind, range, exp, imp, clr, payload, io };
1697
1668
  this.keyup = (e) => {
1698
- if (!this.state.open) return;
1699
- const a = keyEventAllowed(e, keys);
1700
- a === "undo" ? this.time.undo() : a === "redo" ? this.time.redo() : a === "genesis" ? this.time.jumpTo(0) : a === "prevFrame" ? this.time.step(1, false) : a === "nextFrame" ? this.time.step(1, true) : a === "skipBwd" ? this.time.step(5, false) : a === "skipFwd" ? this.time.step(5, true) : a === "rewind" ? this.time.rewind() : a === "playPause" ? this.time[this.time.state.paused ? "play" : "pause"]() : a === "clrHistory" ? this.time.clear() : a === "closeOverlay" ? this.state.open = false : a === "export" ? this.state.import = this.time.export() : a === "import" ? this.state.import.trim().length && this.time.import(this.state.import) : a === "clear" && (this.state.import = "");
1669
+ const a = this.state.open && keyEventAllowed(e, keys);
1670
+ a === "undo" ? this.time.undo() : a === "redo" ? this.time.redo() : a === "genesis" ? this.time.jumpTo(0) : a === "prevFrame" ? this.time.step(1, false) : a === "nextFrame" ? this.time.step(1, true) : a === "skipBwd" ? this.time.step(5, false) : a === "skipFwd" ? this.time.step(5, true) : a === "rewind" ? this.time.rewind() : a === "playPause" ? this.time[s.paused ? "play" : "pause"]() : a === "clrHistory" ? this.time.clear() : a === "closeOverlay" ? this.state.open = false : a === "export" ? this.state.import = this.time.export() : a === "import" ? this.state.import.trim().length && this.time.import(this.state.import) : a === "clear" && (this.state.import = "");
1701
1671
  };
1702
1672
  window.addEventListener("keydown", this.keyup);
1703
1673
  const sync = [
@@ -1707,26 +1677,19 @@ var sia = (() => {
1707
1677
  const dock = getDock(this.config.container);
1708
1678
  if (host.parentNode !== dock) dock.appendChild(host);
1709
1679
  }),
1710
- effect(() => (toggle.textContent = `${this.state.open ? "Hide" : "Show"} ${this.config.title ?? ""}`, panel.hidden = !this.state.open)),
1680
+ effect(() => toggle.textContent = `${(panel.hidden = !this.state.open) ? "Show" : "Hide"} ${title.textContent = this.config.title ?? ""}`),
1681
+ effect(() => playPause.textContent = `${s.paused ? "Play" : "Pause"}${formatKeyForDisplay(keys.shortcuts.playPause)}`),
1711
1682
  effect(() => {
1712
- title.textContent = this.config.title ?? "";
1713
- frame.textContent = `Frame: ${this.time.state.currentFrame} / ${this.time.state.history.length}`;
1714
- history.textContent = `History: ${this.time.state.history.length}`;
1715
- paused.textContent = `Paused: ${this.time.state.paused ? "Yes" : "No"}`;
1716
- playPause.textContent = `${this.time.state.paused ? "Play" : "Pause"}${formatKeyForDisplay(keys.shortcuts.playPause)}`;
1683
+ frame.textContent = `Frame: ${s.currentFrame} / ${s.history.length}`;
1684
+ range.disabled = clrHistory.disabled = !s.history.length;
1685
+ genesis.disabled = rewind.disabled = undo.disabled = !s.currentFrame;
1686
+ playPause.disabled = redo.disabled = s.currentFrame >= s.history.length;
1687
+ range.max = String(s.history.length);
1688
+ range.value = String(Math.min(s.currentFrame, s.history.length));
1689
+ payload.value = JSON.stringify(s.currentFrame ? s.history[s.currentFrame - 1] : { type: "genesis", value: s.initialState }, null, 2);
1717
1690
  }),
1718
1691
  effect(() => {
1719
- clrHistory.disabled = !this.time.state.history.length;
1720
- undo.disabled = this.time.state.currentFrame <= 0;
1721
- redo.disabled = this.time.state.currentFrame >= this.time.state.history.length;
1722
- genesis.disabled = this.time.state.currentFrame <= 0;
1723
- playPause.disabled = this.time.state.currentFrame === this.time.state.history.length;
1724
- rewind.disabled = !this.time.state.currentFrame;
1725
- range.max = String(this.time.state.history.length);
1726
- range.value = String(Math.min(this.time.state.currentFrame, this.time.state.history.length));
1727
- range.disabled = !this.time.state.history.length;
1728
- imp.disabled = !this.state.import.trim().length;
1729
- clr.disabled = !this.state.import.trim().length;
1692
+ clr.disabled = imp.disabled = !this.state.import.trim().length;
1730
1693
  io.value !== this.state.import && (io.value = this.state.import);
1731
1694
  })
1732
1695
  ];
@@ -1,21 +1,21 @@
1
- import { a as REvent, P as Paths, B as BaseReactorPlugin, b as Reactor } from './index-DCG3sacH.js';
1
+ import { a as REvent, P as Paths, B as BaseReactorPlugin, b as Reactor } from './index-Oie9hhE8.js';
2
2
 
3
3
  /** The DNA of a specific moment in time, Records the 'Desire' (Intent) or the 'Fact' (State). */
4
4
  interface HistoryEntry {
5
- /** Was it a 'set' or a 'delete' surgery? */
6
- type: REvent<any, any>["staticType"];
7
5
  /** The surgical address in the Reactor */
8
6
  path: string;
9
7
  /** The data payload at that moment */
10
8
  value: any;
11
9
  /** The "Undo" antidote (Previous value), if applicable */
12
10
  oldValue: any;
13
- /** Did the key for the value exist on its parent object? */
14
- hadKey: boolean;
11
+ /** Was it a 'set' or a 'delete' surgery? */
12
+ type: REvent<any, any>["staticType"];
15
13
  /** Did the Power Line disapprove?; why? */
16
14
  rejected: string;
17
15
  /** For chronological re-enactment */
18
- timestamp: number;
16
+ timedelta: number;
17
+ /** Did the key for the value exist on its parent object? */
18
+ hadKey: boolean;
19
19
  }
20
20
  interface TimeTravelConfig<T extends object = any> {
21
21
  /** Specific paths only, no "*"; instead don't pass anything */
@@ -26,10 +26,10 @@ interface TimeTravelConfig<T extends object = any> {
26
26
  maxPlaybackDelay: number;
27
27
  }
28
28
  interface TimeTravelState {
29
- /** The "Timeline" of mutations (Chronological Log) */
30
- history: HistoryEntry[];
31
29
  /** The "Genesis" snapshot (Raw Data) */
32
30
  initialState: any;
31
+ /** The "Timeline" of mutations (Chronological Log) */
32
+ history: HistoryEntry[];
33
33
  /** The manual playhead (Index in the Timeline) */
34
34
  currentFrame: number;
35
35
  /** Whether playback is currently paused (Automatic Replay) */
@@ -41,6 +41,7 @@ interface TimeTravelState {
41
41
  */
42
42
  declare class TimeTravelPlugin<T extends object = any> extends BaseReactorPlugin<T, TimeTravelConfig<T>, TimeTravelState> {
43
43
  static readonly plugName = "timeTravel";
44
+ protected lastTimestamp: number;
44
45
  protected playbackTimeoutId: number;
45
46
  constructor(config?: Partial<TimeTravelConfig<T>>, rtr?: Reactor<T>);
46
47
  wire(): void;
@@ -66,7 +67,7 @@ declare class TimeTravelPlugin<T extends object = any> extends BaseReactorPlugin
66
67
  /** Pauses the live VCR playback. */
67
68
  pause: () => void;
68
69
  /** Exports the current session as a JSON string. */
69
- export(): string;
70
+ export(replacer?: ((this: any, key: string, value: any) => any) | (number | string)[] | null, space?: string | number): string;
70
71
  /** Imports a session from a JSON string, allowing you to replay or analyze past states. */
71
72
  import(json: string): void;
72
73
  }