sia-reactor 0.0.33 → 0.0.35

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.
Files changed (41) hide show
  1. package/README.md +173 -33
  2. package/dist/{TimeTravelOverlay-BlVhj8n1.d.cts → TimeTravelConsole-BLDKZ0U1.d.ts} +11 -9
  3. package/dist/{TimeTravelOverlay-D8jAd_Fz.d.ts → TimeTravelConsole-zvu8eqTZ.d.cts} +11 -9
  4. package/dist/adapters/react.cjs +203 -173
  5. package/dist/adapters/react.d.cts +10 -8
  6. package/dist/adapters/react.d.ts +10 -8
  7. package/dist/adapters/react.js +12 -14
  8. package/dist/adapters/vanilla.cjs +352 -322
  9. package/dist/adapters/vanilla.d.cts +4 -4
  10. package/dist/adapters/vanilla.d.ts +4 -4
  11. package/dist/adapters/vanilla.js +7 -9
  12. package/dist/{chunk-RI45W4O6.js → chunk-243U74PP.js} +49 -6
  13. package/dist/{chunk-EZ4VRGYI.js → chunk-2ZYYYSZ4.js} +134 -52
  14. package/dist/chunk-4S44OX5N.js +288 -0
  15. package/dist/{chunk-QPJNSYXT.js → chunk-A3ZCYWWM.js} +67 -43
  16. package/dist/{chunk-6HZSS2TX.js → chunk-OTBKVZ4L.js} +105 -107
  17. package/dist/index-CuLuFYu3.d.cts +222 -0
  18. package/dist/index-D_U8Nai1.d.ts +222 -0
  19. package/dist/{index-CWbDYjby.d.cts → index-rWwvrfdn.d.cts} +446 -358
  20. package/dist/{index-CWbDYjby.d.ts → index-rWwvrfdn.d.ts} +446 -358
  21. package/dist/index.cjs +143 -131
  22. package/dist/index.d.cts +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +2 -2
  25. package/dist/modules.cjs +654 -287
  26. package/dist/modules.d.cts +350 -19
  27. package/dist/modules.d.ts +350 -19
  28. package/dist/modules.js +391 -113
  29. package/dist/styles/{time-travel-overlay.css → time-travel-console.css} +72 -32
  30. package/dist/super.d.ts +802 -487
  31. package/dist/super.global.js +713 -331
  32. package/dist/utils.cjs +151 -61
  33. package/dist/utils.d.cts +17 -11
  34. package/dist/utils.d.ts +17 -11
  35. package/dist/utils.js +30 -20
  36. package/package.json +9 -6
  37. package/dist/chunk-3OT72G7R.js +0 -39
  38. package/dist/chunk-UXQ5NJIO.js +0 -274
  39. package/dist/timeTravel-BsVQ5z7v.d.ts +0 -353
  40. package/dist/timeTravel-DoWtLH_e.d.cts +0 -353
  41. package/dist/{chunk-IBAPWB27.js → chunk-JGEI2Q4M.js} +5 -5
package/README.md CHANGED
@@ -50,8 +50,9 @@ const player = reactive({
50
50
 
51
51
  // Logic layer (capture phase)
52
52
  player.on("intent.playing", (e) => {
53
- if (!ready) return e.reject();
53
+ if (!ready) return e.reject(); // warning optimistic UI
54
54
  player.state.playing = true;
55
+ e.resolve(); // claimed and handled
55
56
  }, { capture: true });
56
57
 
57
58
  // UI layer
@@ -92,7 +93,7 @@ pnpm add sia-reactor
92
93
  import { reactive, Reactor, TERMINATOR } from "sia-reactor";
93
94
 
94
95
  // 2. Deep Object Utilities
95
- import { setAny, getAny, mergeObjs } from "sia-reactor/utils";
96
+ import { setPath, getPath, mergeObjs } from "sia-reactor/utils";
96
97
  ```
97
98
 
98
99
  ---
@@ -103,11 +104,11 @@ import { setAny, getAny, mergeObjs } from "sia-reactor/utils";
103
104
 
104
105
  ```javascript
105
106
  import { reactive, Reactor } from "sia-reactor";
106
- import "sia-reactor/utils"; // deep object helpers (setAny/getAny/deleteAny/inAny/parseAnyObj/fanout/mergeObjs/deepClone/nuke...) take note of `fanout`!
107
+ import "sia-reactor/utils"; // deep object helpers (setPath/getPath/deletePath/hasPath/parsePathObj/fanout/force/mergeObjs/deepClone/nuke...) take note of `fanout`!
107
108
  import "sia-reactor/modules"; // built-in modules + storage adapters
108
- import "sia-reactor/adapters/vanilla"; // Autotracker + effect API + TimeTravelOverlay class
109
+ import "sia-reactor/adapters/vanilla"; // Autotracker + effect API + TimeTravelConsole class
109
110
  import "sia-reactor/adapters/react"; // useReactor/useSelector/usePath hooks
110
- import "sia-reactor/styles/time-travel-overlay.css"; // TimeTravelOverlay CSS
111
+ import "sia-reactor/styles/time-travel-console.css"; // TimeTravelConsole CSS
111
112
  ```
112
113
 
113
114
  ### CDN / Browser (Global)
@@ -165,7 +166,7 @@ All methods are available on `Reactor` instances or objects wrapped in `reactive
165
166
  - **`watch(path, callback, options)` <-> `nowatch(path, callback)`**: Fires instantly after a mutation. Use strictly for critical internal engine syncing on leaf paths preferably, sees only direct operations.
166
167
 
167
168
  #### **Listeners (Asynchronous/Batched UI Observers)**
168
- - **`on(path, callback, options)` <-> `off(path, callback, options)`**: Attach DOM-style event listeners that respect `depth`. Supports `{ capture: true, depth: 1, once: true, immediate: true }`.
169
+ - **`on(path, callback, options)` <-> `off(path, callback, options)`**: Attach DOM-style event listeners that respect `depth`. Supports `{ capture: true, depth: 1, once: true, init: true }`.
169
170
  - **`once(path, callback, options)`**: Fires once and self-destructs, others have too: `sonce(...)`, `gonce(...)`, `donce(...)`, `wonce(...)`.
170
171
 
171
172
  #### **Lifecycle & Utilities**
@@ -200,22 +201,23 @@ The engine provides native React bindings utilizing `useSyncExternalStore` and a
200
201
 
201
202
  ```javascript
202
203
  import { reactive } from "sia-reactor";
203
- import { useReactor, useAnyReactor, useSelector, useAnySelector, usePath, effect } from "sia-reactor/adapters/react";
204
+ import { useReactor, useReactorSnapshot, useAnyReactor, useSelector, useSelectorSnapshot, useAnySelector, usePath, effect } from "sia-reactor/adapters/react";
204
205
 
205
206
  const store = reactive({ user: { name: "Kosi", age: 25 }, theme: "dark" });
206
207
 
207
208
  // 1. The Tracked State (Valtio-style)
208
209
  function Profile() {
209
- const sameStore = useReactor(store); // `useReactorSnapshot()` if mutable issues arise
210
+ const sameStore = useReactor(store); // `useReactorSnapshot()` if mutable issues arise, e.g. rare zombie child
210
211
  useAnyReactor(); // when you just want state from any reactor
211
- // Only re-renders if store.user.name mutates. Completely ignores age and theme!
212
- return <div>{sameStore.user.name + otherStore.user.name}</div>;
212
+
213
+ return <div>{sameStore.user.name + otherStore.user.name}</div>; // Only re-renders if store.user.name mutates. Completely ignores age and theme!
213
214
  } // no snapshots like Valtio, you can read or write to anything
214
215
 
215
216
  // 2. The Slice Selector (Zustand-style)
216
217
  function Theme() {
217
218
  const theme = useSelector(store, (s) => s.theme); // `useSelectorSnapshot()` if mutable issues arise
218
219
  const newName = useAnySelector(() => store.user.name + spouseStore.user.name); // when you just want to derive any state from any reactor
220
+
219
221
  return <div>Theme: {theme}</div>;
220
222
  }
221
223
 
@@ -226,15 +228,17 @@ function AgeObserver() {
226
228
  }
227
229
 
228
230
  // 4. Vanilla Side Effects (Runs anywhere, framework agnostic)
229
- const stopTracking = effect(() => console.log("User name changed to:", store.user.name)); // read or write as you wish
231
+ const stopTracking = effect(() => console.log("User name changed to:", store.user.name), { sync: false });
232
+
230
233
  ```
234
+ *NOTE: They support all listener and watcher options with an additional `sync: boolean` to switch between `on` and `watch` behavior.*
231
235
 
232
236
  ### Modules: The Extension Port
233
237
 
234
- The `Reactor` is designed to be a lightweight core. Extended capabilities are attached via Modules. Use `.attach(target: Reactor | Reactive<T>, id)` to chain reactors then `.setup(target, id)` which the `Reactor.use()` also calls to init, the `id` param will be direct keys on the final object, pass dotted paths to manipulate the shape.
238
+ The `Reactor` is designed to be a lightweight core. Extended capabilities are attached via Modules. Use `.attach(target: Reactor | Reactive<T>, id)` to chain reactors then `.setup(target, id)` which the `Reactor.use()` also calls to init, the `id` param will be direct keys on the final object, pass dotted paths to manipulate the shape. `this` context is preserved for module methods, they're auto-bound.
235
239
 
236
240
  #### The Persistence Module
237
- Automatically syncs your State to LocalStorage, SessionStorage, Memory or IndexedDB. Always use this module first to avoid re-initialization issues.
241
+ Automatically syncs your State to LocalStorage, SessionStorage, Memory or IndexedDB. Always `use` this module first to avoid re-initialization issues.
238
242
 
239
243
  ```javascript
240
244
  import { reactive, Reactor, getReactor } from "sia-reactor";
@@ -248,6 +252,9 @@ const persist = new PersistModule({
248
252
  throttle: 2500, // ms between saves
249
253
  fanout: true, // async hydration use leaf writes to sync initialized listeners.
250
254
  adapter: new IndexedDBAdapter({ dbName: "Session", version: 1, onversionchange: () => location.reload(), useSnapshot: true }) // or `LocalStorageAdapter` (instance or signature)
255
+ mirrorReads: true, // intents are requests, listen on their changes but only their state factual mirror is reliable data
256
+ mirrorWrites: true, // states are facts, can be stored but only their intent live mirror is reliable for effects, e.g. hydration
257
+ cachePayload: true // store the initial hydration payload to hydrate late-attaching reactors, free memory with `.clearCache()`
251
258
  }, getReactor(store)); // `Reactor` in second arg for path inference
252
259
  store.use(persist); // calls `.setup()`, use after all attachments, `id` is the second param too.
253
260
 
@@ -258,29 +265,42 @@ persist.config.whitelist = { ui: ["settings.theme"], app: ["settings.volume"] };
258
265
  ```
259
266
 
260
267
  #### The Time Travel Module
261
- Record state frames, step through history, and optionally attach a ready-to-use vanilla debug overlay. Beware of paradoxes, seperate intent from state even in time.
268
+ Record state frames, step through history, and optionally attach a ready-to-use vanilla debug overlay. Beware of paradoxes, one timeline is used by default to keep things linear and predictable.
262
269
 
263
270
  ```javascript
264
271
  import { TimeTravelModule } from "sia-reactor/modules";
265
- import { effect, TimeTravelOverlay } from "sia-reactor/adapters/vanilla";
266
- import "sia-reactor/css/time-travel-overlay.css";
272
+ import { effect, TimeTravelConsole } from "sia-reactor/adapters/vanilla";
273
+ import "sia-reactor/css/time-travel-console.css";
274
+
275
+ const time = new TimeTravelModule({
276
+ limit: 300, // max frames to keep in memory, older frames are dropped
277
+ loop: false, // if true, stepping past the last frame will wrap to the first frame and vice versa
278
+ playbackRate: 150, // multiplier for the delay between events during playback
279
+ whitelist: ["store.playing", "store.currentTime"], // all paths if omitted, use object if multiple reactors
280
+ beforeEntry: composeHeuristics(
281
+ createTxPathMerger(), // O(1) compression of duplicate paths inside transaction envelopes
282
+ createTextBundler({ whitelist: ["store.search.query", "store.profile.bio"] }) // Smart string diffing for text inputs
283
+ ) // Intercept and compress history in-flight before it settles!
284
+ });
285
+ store.use(time); // can chain (.use(persist).use(time))
267
286
 
268
- const time = new TimeTravelModule({ maxHistory: 300, loop: false, rate: 150, whitelist: ["store.playing", "store.currentTime"] });
269
- store.use(time);
287
+ // If persist uses an async adapter (e.g. IndexedDB), stop tracking till after hydration:
288
+ time.untrack(); // immediately after or at creation (new Time...ule.().untrack())
289
+ persist.state.once("hydrated", time.track); // `hydrated` starts `false`, wait until it flips.
290
+ effect(() => persist.state.hydrated && time.track(), { once: true }) // same logic, different look :)
270
291
 
271
- // If persist uses an async adapter (e.g. IndexedDB), wait till after hydration:
272
- persist.state.once("hydrated", () => store.use(time)); // starts `false`, one-time stall until it flips
273
- effect(() => persist.state.hydrated && store.use(time), { once: true }) // same logic, different look :)
292
+ // Here's a cool trick:
293
+ persist.attach(time.state, "timeTravel.state") // now, history will survive reloads
274
294
 
275
- const overlay = new TimeTravelOverlay(time, { color: "#e26e02", startOpen: false, devOnly: true, container: document.body }); // optional debug interface for visulazation
295
+ const overlay = new TimeTravelConsole(time, { color: "#e26e02", startOpen: false, devOnly: true, container: document.body }); // optional debug interface for visualization
276
296
  ```
277
297
  ```jsx
278
- import { TimeTravelOverlay } from "sia-reactor/adapters/react";
298
+ import { TimeTravelConsole } from "sia-reactor/adapters/react";
279
299
 
280
- <TimeTravelOverlay time={time} color="#e26e02" startOpen devOnly /> // react-safe instance lifecycle management, e.g. for HMR predictability.
300
+ <TimeTravelConsole time={time} color="#e26e02" startOpen devOnly /> // react-safe instance lifecycle management, e.g. for HMR predictability.
281
301
  ```
282
302
 
283
- Useful methods: `play()`, `pause()`, `rewind()`, `clear()`, `undo()`, `redo()`, `step(n, forward)`, `jumpTo(frame)`, `export(replacer)`, `import(json, reviver)`.
303
+ Useful methods: `play()`, `pause()`, `rewind()`, `undo()`, `redo()`, `step(n, forward)`, `jumpTo(frame)`, `track()`, `untrack()`, `clear()`, `export(replacer)`, `import(json, reviver)`.
284
304
 
285
305
  ### Reactor Build Options
286
306
 
@@ -289,9 +309,11 @@ These are some core build options accepted by `new Reactor(core, build)` and `re
289
309
  - **`debug?`**: 1-time set. Enables debug logging and diagnostics of core operations. (default: `false`)
290
310
  - **`crossRealms?`**: Enables cross-realm object detection support by using slower but safer type checks. (e.g. iframes) (default: `false`).
291
311
  - **`smartCloning?`**: Enables structural-sharing snapshot behavior (requires `referenceTracking: true`) (default: `false`).
292
- - **`eventBubbling?`**: Enables event bubbling across ancestor paths (default: `true`) (default: `true`).
312
+ - **`eventBubbling?`**: Enables event bubbling across ancestor paths (default: `true`).
313
+ - **`eventCapturing?`**: Enables event capturing across ancestor paths, `"auto"` is `true` for rejectable events. (default: `"auto"`).
293
314
  - **`lineageTracing?`**: Enables path lineage tracing for reference lookups on property access (requires `referenceTracking: true`) (default: `false`).
294
315
  - **`preserveContext?`**: Preserves Reflect trap context; safer with ~8x slowdown in hot paths, allows more types to be proxied (e.g. classes) (default: `false`).
316
+ - **`eventTimeStamps?`**: Enables high resolution timestamps on events (default: `false`).
295
317
  - **`equalityFunction?`**: Custom equality used by setters and adapter comparisons (default: `Object.is`).
296
318
  - **`batchingFunction?`**: Custom batching scheduler for listener notification flushes (default: `queueMicrotask`)
297
319
  - **`referenceTracking?`**: Enables identity/reference tracking features in the runtime. (default: `false`).
@@ -352,8 +374,8 @@ player.on("intent.playing", (e) => {
352
374
  ### Troubleshooting
353
375
 
354
376
  - Listener timing feels late: `on(path, ...)` is microtask-batched by design; use `watch(path, ...)` only for strict immediate engine sync on leaf paths preferably.
355
- - Listeners don't react to changes: use `fanout(target, object, { depth: n })` instead of direct object sets to keep immutable semantics.
356
- - `reject()` appears ignored: call it in capture phase and ensure branch is wrapped in `intent(...)`, also remember it's the listener's choice to comply.
377
+ - Listeners don't react to changes: use `fanout(target, object, { depth: n })` instead of direct object sets to keep immutable semantics or `force(() => ...)` to bypass equality checks.
378
+ - `reject()` appears ignored: call it in capture phase and ensure branch is wrapped in `intent(...)`, also remember it's the listeners' choice to comply.
357
379
  - Snapshot behavior feels stale: enable `referenceTracking: true` with `smartCloning: true`, also use these when persisting to environments that don't take proxies, e.g. IndexedDB.
358
380
  - Cross-frame data is skipped: enable `crossRealms: true` for iframe/other realm objects.
359
381
  - Class/prototype behavior is odd: enable `preserveContext: true` (tradeoff: slower hot paths).
@@ -376,8 +398,8 @@ rtr.set("user.age", (value, terminated, payload) => {
376
398
  console.log(payload.type); // "set" | "get" | "delete"
377
399
  console.log(payload.target); // The exact anatomy of the mutation (see below)
378
400
  console.log(payload.root); // Reference to the entire state tree
379
- console.log(payload.terminated); // Boolean: Did a previous mediator kill this action?
380
401
  console.log(payload.rejectable); // Boolean: Is this target wrapped in `intent()`?
402
+ console.log(payload.terminated); // Boolean: Did a previous mediator kill this action?
381
403
  }); // you could use external callbacks but typed with `Payload<T, "user.age">`
382
404
  rtr.get("user.age", (value, payload) => {});
383
405
  rtr.delete("user.age", (terminated, payload) => {});
@@ -418,7 +440,7 @@ If you mutate `store.user.profile.name = "Kosi"`, the event wave travels like th
418
440
  2. **Target Phase:** `user.profile.name`
419
441
  3. **Bubble Phase:** `user.profile` ➔ `user` ➔ `*` (Root)
420
442
 
421
- *NOTE: Only `on` does this since it is batched to stay within recursive limits.*
443
+ *NOTE: Only `on` does this since it is batched to stay within recursive limits. There are `Reactor` options (`eventCapturing`, `eventBubbling`) that toggle phases. See [details](#reactor-build-options) above.*
422
444
 
423
445
  #### The Event Anatomy (`REvent` type)
424
446
  Listeners receive a `ReactorEvent` (`REvent`). This object *inherits* everything from the `Payload`, but adds **Political Event Routing**, providing absolute surgical awareness of what is happening in the tree.
@@ -434,8 +456,11 @@ rtr.on("user.profile", (e) => {
434
456
  console.log(e.oldValue); // "John" (The previous value)
435
457
  // 2. Political Routing
436
458
  console.log(e.eventPhase); // 3 (Bubbling Phase)
437
- console.log(e.bubbles); // true/false
459
+ console.log(e.bubbles); // true/false (configure via eventBubbling option)
460
+ console.log(e.captures); // true/false (configure via eventCapturing option)
461
+ console.log(e.rejectable); // true/false (Is this from an intent() path that can be rejected?)
438
462
  // 3. Misc
463
+ console.log(e.timestamp); // 1697059200000 (`DOMHighResTimeStamp`, configurable via `eventTimestamp` option)
439
464
  console.log(e.composedPath()); // ["Kosi", { name: "Kosi", age: 26 }, { profile: { name: "Kosi", age: 26 } }, { user: { profile: { name: "Kosi", age: 26 } } }] (refs, target -> root)
440
465
  }); // you could use external callbacks but typed with `REvent<T, "user.age">`
441
466
  ```
@@ -467,7 +492,7 @@ To help you instantly differentiate between the object *itself* being replaced,
467
492
  * If `store.user.profile = {}` happens, the listener receives `e.type === "set"`.
468
493
  * If `store.user.profile.name = "Kosi"` happens, the parent listener receives `e.type === "update"`.
469
494
 
470
- This allows for highly fine-grained syncing bridges across your application without writing heavy for-loop diffing algorithms! Use `{ depth: n }` to control how deep the path bubbles you see are, i.e.
495
+ This allows for highly fine-grained syncing bridges across your application without writing heavy for-loop diffing algorithms! Use `{ depth: n }` to control how deep the path bubbles you see are:
471
496
 
472
497
  ```javascript
473
498
  rtr.on("todos", (e) => console.log(e), { depth : 1 }); // only sees updates on direct children
@@ -486,13 +511,128 @@ rtr.on("todos", (e: REvent<User, "todos", 1>) => {
486
511
  const { path, key } = e.target;
487
512
  console.log(path, key); // or e.target.path, e.target.key
488
513
  }
489
- }, { depth: 1 }); // you need the generic for external callbacks only
514
+ }, { depth: 1 }); // REvent generic is used for external callbacks
490
515
  ```
516
+ *NOTE: Use with caution, it can be blind to fanouts, e.g during async hydration. Advised for just arrays as theirs default to atomic, i.e. 1-level deep (`.length`). For rare cases, cast `e` to a desired depth in the callback after custom conditions, e.g. `getDepth(e.target.path)` > (getDepth(e.currentTarget.path) + 1).*
491
517
 
492
518
  ---
493
519
 
494
520
  ## Architectural Tricks
495
521
 
522
+ ### Perks worth Highlighting
523
+
524
+ #### 1. Transactions & Grouping
525
+ By default, every mutation is recorded as a single frame. For UI gestures like dragging a slider, you want to group hundreds of rapid mutations into a single Undo/Redo step for the `TimeTravelModule`.
526
+
527
+ Transactions natively support **deep nesting** and preserve your semantic labels. When paired with `createTxPathMerger`, duplicate paths inside these envelopes are compressed in-flight with zero overhead.
528
+
529
+ ```javascript
530
+ import { transaction, startTx, endTx } from "sia-reactor/modules";
531
+
532
+ // Option A: Synchronous Grouping
533
+ transaction(() => {
534
+ store.user.name = "Kosi";
535
+ store.user.age = 19;
536
+
537
+ transaction(() => {
538
+ store.user.pretty = true;
539
+ }, "Countenance Update"); // Deeply nested transactions are isolated and fully supported
540
+ }, "Profile Update"); // All mutations undo/redo as one semantic envelope, fanout uses this internally
541
+
542
+ // Option B: Multi-Tick Gestures (e.g., Slider Drag)
543
+ let tx;
544
+ slider.addEventListener("pointerdown", () => tx = startTx("Volume Scrub"));
545
+ slider.addEventListener("pointerup", () => endTx(tx)); // Thousands of slider mutations happen here, natively compressed by `createTxPathMerger`
546
+
547
+ ```
548
+
549
+ #### 2. Heuristics & Text Bundling
550
+ Text inputs generate a massive amount of rapid, noisy history. However, if you are building a **truly deterministic, replayable time machine**, you cannot rely on the browser's isolated history. You must capture the text *and* the cursor state, and bundle rapid keystrokes into semantic, human-readable chunks.
551
+
552
+ The `createTextBundler` heuristic intelligently groups keystrokes and respects word boundaries. You pair this with `setValueWithCursor` to flawlessly sync the DOM. You are essentially building your own browser input physics, skip if using basic controlled or uncontrolled inputs.
553
+
554
+ ```javascript
555
+ import { TimeTravelModule } from "sia-reactor/modules";
556
+ import { createTextBundler, setValueWithCursor } from "sia-reactor/modules/timeTravel/heuristics";
557
+ import { effect } from "sia-reactor/adapters/vanilla";
558
+
559
+ // 1. The Setup: State storing [text, selectionStart, selectionEnd, selectionDirection]
560
+ const chatTime = new TimeTravelModule({
561
+ whitelist: ["chatbox"],
562
+ beforeEntry: createTextBundler({
563
+ toString: (v) => v[0], // Extract the string at index [0] so the bundler can intelligently diff the typing
564
+ throttle: 700, // max delay between edits
565
+ boundaryRegex: /[\s.,!?;:\n()[\]{}'"`]/, // chars considered "hard boundaries"
566
+ maxGrowth: 48, // prevents giant paragraph merging
567
+ bundleInserts: true, // `true` for chat apps; `false` for code editors
568
+ bundleDeletes: true, // `true` for most use cases
569
+ strictMerges: true, // typing -> deleting -> typing becomes separate frames
570
+ })
571
+ });
572
+ store.use(chatTime);
573
+
574
+ // 2. The UI Integration (React Example)
575
+ import { keyEventAllowed } from "sia-reactor/utils";
576
+
577
+ function ChatInput() {
578
+ const s = useReactor(store);
579
+ const ref = useRef(null);
580
+ const keySettings = { overrides: ["ctrl+z", "meta+z"], shortcuts: { undo: "ctrl+z", redo: ["ctrl+y", "ctrl+shift+z", "meta+shift+z"] }, blocks: ["ctrl+shift+r"] }; // more info in editor tooltips
581
+
582
+ useEffect(() => {
583
+ return effect(() => {
584
+ const [text, start, end, dir] = store.chatbox; // works perfectly during playback as module updates the store.
585
+ setValueWithCursor(ref.current, text, start, end, dir);
586
+ }); // it's not React's state hence our `effect` API, cleans up on unmount
587
+ }, []);
588
+
589
+ return (
590
+ <textarea
591
+ ref={ref}
592
+ defaultValue={s.chatbox[0]}
593
+ onInput={(e) => {
594
+ const t = e.target;
595
+ s.chatbox = [t.value, t.selectionStart, t.selectionEnd, t.selectionDirection]; // Save the raw text alongside the user's exact cursor selection bounds
596
+ }}
597
+ onKeyDown={(e) => {
598
+ const action = keyEventAllowed(e, keySettings); // utility to interpret key combos according to your settings
599
+ action === "undo" ? chatTime.undo() : action === "redo" && chatTime.redo(); // trigger time travel actions accordingly.
600
+ }}
601
+ />
602
+ );
603
+ } // This is how you take over the browser
604
+
605
+ ```
606
+
607
+ #### 3. The Meta Context
608
+
609
+ S.I.A. Reactor features a high-performance, synchronous meta-context engine. It allows you to inject temporary data (like flags or transaction IDs) into the event loop without polluting your core state even without function signatures.
610
+
611
+ ```javascript
612
+ import { withMeta } from "sia-reactor/utils";
613
+ import { silence } from "sia-reactor/modules";
614
+
615
+ // 1. The Silence Wrapper: Executes mutations that the TimeTravelModule will not record. same as withMeta({ silent: true }, () => ...)
616
+ silence(() => {
617
+ player.state.volume = 100;
618
+ }); // Useful for internal side-effects that shouldn't ruin the undo/redo history.
619
+
620
+ // 2. Custom Meta Injection: Injects data seamlessly into the ReactorEvent payload for listeners to read.
621
+ withMeta({ customSource: "gamepad" }, () => {
622
+ player.intent.playing = true;
623
+ });
624
+
625
+ player.on("intent.playing", (e) => {
626
+ console.log(e.customSource); // "gamepad"
627
+ });
628
+
629
+ declare module "sia-reactor" {
630
+ interface ReactorEventMeta {
631
+ customSource?: "gamepad" | "api" | string;
632
+ }
633
+ }
634
+ ```
635
+
496
636
  ### The CSS Black Box
497
637
 
498
638
  Imagine you have 50 different CSS variables in your state (`settings.css.containerWidth`, `settings.css.themeColor`, etc.). Registering 50 individual `watch()` or `on()` listeners would need manual css crawling that will be blind to dynamically added variables.
@@ -1,8 +1,8 @@
1
- import { e as Reactive } from './index-CWbDYjby.cjs';
2
- import { m as TimeTravelModule } from './timeTravel-DoWtLH_e.cjs';
1
+ import { R as Reactive } from './index-rWwvrfdn.js';
2
+ import { T as TimeTravelModule } from './index-D_U8Nai1.js';
3
3
 
4
4
  /** Reactive options for the TimeTravel overlay instance. */
5
- interface TimeTravelOverlayConfig {
5
+ interface TimeTravelConsoleConfig {
6
6
  /** Header text shown at the top of the overlay panel. */
7
7
  title: string;
8
8
  /** Accent color used to derive panel theme variables. */
@@ -19,24 +19,26 @@ interface TimeTravelOverlayConfig {
19
19
  * - Mounts a docked HUD into the configured container, syncs its UI with module state, and forwards keyboard/button actions to the TimeTravelModule.
20
20
  * Supports reactive `config` updates (title/color/container/devOnly) and maintains local overlay UI state (`open` and `import` payload text).
21
21
  */
22
- declare class TimeTravelOverlay {
22
+ declare class TimeTravelConsole {
23
23
  static count: number;
24
24
  index: number;
25
- config: TimeTravelOverlayConfig;
25
+ config: Reactive<TimeTravelConsoleConfig>;
26
26
  readonly state: Reactive<{
27
27
  open: boolean;
28
28
  import: string;
29
+ stride: number;
29
30
  }, undefined>;
30
31
  readonly time: TimeTravelModule;
31
- readonly els: Record<string, HTMLElement>;
32
+ readonly host: HTMLElement;
32
33
  private clups;
33
- private keyup?;
34
+ private keydown;
35
+ private keyup;
34
36
  /** Creates a docked TimeTravel overlay bound to a module instance.
35
37
  * @param time TimeTravel module instance that owns timeline operations.
36
38
  * @param build Optional initial overlay config overrides.
37
39
  */
38
- constructor(time: TimeTravelModule, build?: Partial<TimeTravelOverlayConfig>);
40
+ constructor(time: TimeTravelModule, build?: Partial<TimeTravelConsoleConfig>);
39
41
  destroy(): void;
40
42
  }
41
43
 
42
- export { TimeTravelOverlay as T, type TimeTravelOverlayConfig as a };
44
+ export { TimeTravelConsole as T, type TimeTravelConsoleConfig as a };
@@ -1,8 +1,8 @@
1
- import { e as Reactive } from './index-CWbDYjby.js';
2
- import { m as TimeTravelModule } from './timeTravel-BsVQ5z7v.js';
1
+ import { R as Reactive } from './index-rWwvrfdn.cjs';
2
+ import { T as TimeTravelModule } from './index-CuLuFYu3.cjs';
3
3
 
4
4
  /** Reactive options for the TimeTravel overlay instance. */
5
- interface TimeTravelOverlayConfig {
5
+ interface TimeTravelConsoleConfig {
6
6
  /** Header text shown at the top of the overlay panel. */
7
7
  title: string;
8
8
  /** Accent color used to derive panel theme variables. */
@@ -19,24 +19,26 @@ interface TimeTravelOverlayConfig {
19
19
  * - Mounts a docked HUD into the configured container, syncs its UI with module state, and forwards keyboard/button actions to the TimeTravelModule.
20
20
  * Supports reactive `config` updates (title/color/container/devOnly) and maintains local overlay UI state (`open` and `import` payload text).
21
21
  */
22
- declare class TimeTravelOverlay {
22
+ declare class TimeTravelConsole {
23
23
  static count: number;
24
24
  index: number;
25
- config: TimeTravelOverlayConfig;
25
+ config: Reactive<TimeTravelConsoleConfig>;
26
26
  readonly state: Reactive<{
27
27
  open: boolean;
28
28
  import: string;
29
+ stride: number;
29
30
  }, undefined>;
30
31
  readonly time: TimeTravelModule;
31
- readonly els: Record<string, HTMLElement>;
32
+ readonly host: HTMLElement;
32
33
  private clups;
33
- private keyup?;
34
+ private keydown;
35
+ private keyup;
34
36
  /** Creates a docked TimeTravel overlay bound to a module instance.
35
37
  * @param time TimeTravel module instance that owns timeline operations.
36
38
  * @param build Optional initial overlay config overrides.
37
39
  */
38
- constructor(time: TimeTravelModule, build?: Partial<TimeTravelOverlayConfig>);
40
+ constructor(time: TimeTravelModule, build?: Partial<TimeTravelConsoleConfig>);
39
41
  destroy(): void;
40
42
  }
41
43
 
42
- export { TimeTravelOverlay as T, type TimeTravelOverlayConfig as a };
44
+ export { TimeTravelConsole as T, type TimeTravelConsoleConfig as a };