sia-reactor 0.0.34 → 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 +170 -31
  2. package/dist/{TimeTravelOverlay-DTqM5uJB.d.cts → TimeTravelConsole-BLDKZ0U1.d.ts} +11 -9
  3. package/dist/{TimeTravelOverlay-cSlHzoKB.d.ts → TimeTravelConsole-zvu8eqTZ.d.cts} +11 -9
  4. package/dist/adapters/react.cjs +196 -164
  5. package/dist/adapters/react.d.cts +10 -8
  6. package/dist/adapters/react.d.ts +10 -8
  7. package/dist/adapters/react.js +10 -12
  8. package/dist/adapters/vanilla.cjs +346 -314
  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-4PLMBUCP.js → chunk-2ZYYYSZ4.js} +122 -36
  14. package/dist/chunk-4S44OX5N.js +288 -0
  15. package/dist/{chunk-VIDZLTP2.js → chunk-A3ZCYWWM.js} +67 -43
  16. package/dist/{chunk-T2CAL5F4.js → chunk-OTBKVZ4L.js} +99 -103
  17. package/dist/index-CuLuFYu3.d.cts +222 -0
  18. package/dist/index-D_U8Nai1.d.ts +222 -0
  19. package/dist/{index-0TjDsae1.d.cts → index-rWwvrfdn.d.cts} +436 -348
  20. package/dist/{index-0TjDsae1.d.ts → index-rWwvrfdn.d.ts} +436 -348
  21. package/dist/index.cjs +137 -123
  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 +643 -278
  26. package/dist/modules.d.cts +350 -19
  27. package/dist/modules.d.ts +350 -19
  28. package/dist/modules.js +386 -112
  29. package/dist/styles/{time-travel-overlay.css → time-travel-console.css} +72 -32
  30. package/dist/super.d.ts +787 -472
  31. package/dist/super.global.js +696 -316
  32. package/dist/utils.cjs +134 -40
  33. package/dist/utils.d.cts +17 -11
  34. package/dist/utils.d.ts +17 -11
  35. package/dist/utils.js +21 -11
  36. package/package.json +7 -4
  37. package/dist/chunk-3OT72G7R.js +0 -39
  38. package/dist/chunk-CEPDD5XN.js +0 -274
  39. package/dist/timeTravel-CX100S8f.d.ts +0 -353
  40. package/dist/timeTravel-wU23uudD.d.cts +0 -353
  41. package/dist/{chunk-IBAPWB27.js → chunk-JGEI2Q4M.js} +5 -5
package/README.md CHANGED
@@ -104,11 +104,11 @@ import { setPath, getPath, mergeObjs } from "sia-reactor/utils";
104
104
 
105
105
  ```javascript
106
106
  import { reactive, Reactor } from "sia-reactor";
107
- import "sia-reactor/utils"; // deep object helpers (setPath/getPath/deletePath/hasPath/parsePathObj/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`!
108
108
  import "sia-reactor/modules"; // built-in modules + storage adapters
109
- import "sia-reactor/adapters/vanilla"; // Autotracker + effect API + TimeTravelOverlay class
109
+ import "sia-reactor/adapters/vanilla"; // Autotracker + effect API + TimeTravelConsole class
110
110
  import "sia-reactor/adapters/react"; // useReactor/useSelector/usePath hooks
111
- import "sia-reactor/styles/time-travel-overlay.css"; // TimeTravelOverlay CSS
111
+ import "sia-reactor/styles/time-travel-console.css"; // TimeTravelConsole CSS
112
112
  ```
113
113
 
114
114
  ### CDN / Browser (Global)
@@ -166,7 +166,7 @@ All methods are available on `Reactor` instances or objects wrapped in `reactive
166
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.
167
167
 
168
168
  #### **Listeners (Asynchronous/Batched UI Observers)**
169
- - **`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 }`.
170
170
  - **`once(path, callback, options)`**: Fires once and self-destructs, others have too: `sonce(...)`, `gonce(...)`, `donce(...)`, `wonce(...)`.
171
171
 
172
172
  #### **Lifecycle & Utilities**
@@ -201,22 +201,23 @@ The engine provides native React bindings utilizing `useSyncExternalStore` and a
201
201
 
202
202
  ```javascript
203
203
  import { reactive } from "sia-reactor";
204
- 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";
205
205
 
206
206
  const store = reactive({ user: { name: "Kosi", age: 25 }, theme: "dark" });
207
207
 
208
208
  // 1. The Tracked State (Valtio-style)
209
209
  function Profile() {
210
- const sameStore = useReactor(store); // `useReactorSnapshot()` if mutable issues arise
210
+ const sameStore = useReactor(store); // `useReactorSnapshot()` if mutable issues arise, e.g. rare zombie child
211
211
  useAnyReactor(); // when you just want state from any reactor
212
- // Only re-renders if store.user.name mutates. Completely ignores age and theme!
213
- 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!
214
214
  } // no snapshots like Valtio, you can read or write to anything
215
215
 
216
216
  // 2. The Slice Selector (Zustand-style)
217
217
  function Theme() {
218
218
  const theme = useSelector(store, (s) => s.theme); // `useSelectorSnapshot()` if mutable issues arise
219
219
  const newName = useAnySelector(() => store.user.name + spouseStore.user.name); // when you just want to derive any state from any reactor
220
+
220
221
  return <div>Theme: {theme}</div>;
221
222
  }
222
223
 
@@ -227,15 +228,17 @@ function AgeObserver() {
227
228
  }
228
229
 
229
230
  // 4. Vanilla Side Effects (Runs anywhere, framework agnostic)
230
- 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
+
231
233
  ```
234
+ *NOTE: They support all listener and watcher options with an additional `sync: boolean` to switch between `on` and `watch` behavior.*
232
235
 
233
236
  ### Modules: The Extension Port
234
237
 
235
- 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.
236
239
 
237
240
  #### The Persistence Module
238
- 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.
239
242
 
240
243
  ```javascript
241
244
  import { reactive, Reactor, getReactor } from "sia-reactor";
@@ -249,6 +252,9 @@ const persist = new PersistModule({
249
252
  throttle: 2500, // ms between saves
250
253
  fanout: true, // async hydration use leaf writes to sync initialized listeners.
251
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()`
252
258
  }, getReactor(store)); // `Reactor` in second arg for path inference
253
259
  store.use(persist); // calls `.setup()`, use after all attachments, `id` is the second param too.
254
260
 
@@ -259,29 +265,42 @@ persist.config.whitelist = { ui: ["settings.theme"], app: ["settings.volume"] };
259
265
  ```
260
266
 
261
267
  #### The Time Travel Module
262
- 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.
263
269
 
264
270
  ```javascript
265
271
  import { TimeTravelModule } from "sia-reactor/modules";
266
- import { effect, TimeTravelOverlay } from "sia-reactor/adapters/vanilla";
267
- 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))
268
286
 
269
- const time = new TimeTravelModule({ maxHistory: 300, loop: false, rate: 150, whitelist: ["store.playing", "store.currentTime"] });
270
- 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 :)
271
291
 
272
- // If persist uses an async adapter (e.g. IndexedDB), wait till after hydration:
273
- persist.state.once("hydrated", () => store.use(time)); // starts `false`, one-time stall until it flips. i.e, also escapes custom initialization
274
- 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
275
294
 
276
- 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
277
296
  ```
278
297
  ```jsx
279
- import { TimeTravelOverlay } from "sia-reactor/adapters/react";
298
+ import { TimeTravelConsole } from "sia-reactor/adapters/react";
280
299
 
281
- <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.
282
301
  ```
283
302
 
284
- 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)`.
285
304
 
286
305
  ### Reactor Build Options
287
306
 
@@ -290,9 +309,11 @@ These are some core build options accepted by `new Reactor(core, build)` and `re
290
309
  - **`debug?`**: 1-time set. Enables debug logging and diagnostics of core operations. (default: `false`)
291
310
  - **`crossRealms?`**: Enables cross-realm object detection support by using slower but safer type checks. (e.g. iframes) (default: `false`).
292
311
  - **`smartCloning?`**: Enables structural-sharing snapshot behavior (requires `referenceTracking: true`) (default: `false`).
293
- - **`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"`).
294
314
  - **`lineageTracing?`**: Enables path lineage tracing for reference lookups on property access (requires `referenceTracking: true`) (default: `false`).
295
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`).
296
317
  - **`equalityFunction?`**: Custom equality used by setters and adapter comparisons (default: `Object.is`).
297
318
  - **`batchingFunction?`**: Custom batching scheduler for listener notification flushes (default: `queueMicrotask`)
298
319
  - **`referenceTracking?`**: Enables identity/reference tracking features in the runtime. (default: `false`).
@@ -353,8 +374,8 @@ player.on("intent.playing", (e) => {
353
374
  ### Troubleshooting
354
375
 
355
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.
356
- - Listeners don't react to changes: use `fanout(target, object, { depth: n })` instead of direct object sets to keep immutable semantics.
357
- - `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.
358
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.
359
380
  - Cross-frame data is skipped: enable `crossRealms: true` for iframe/other realm objects.
360
381
  - Class/prototype behavior is odd: enable `preserveContext: true` (tradeoff: slower hot paths).
@@ -377,8 +398,8 @@ rtr.set("user.age", (value, terminated, payload) => {
377
398
  console.log(payload.type); // "set" | "get" | "delete"
378
399
  console.log(payload.target); // The exact anatomy of the mutation (see below)
379
400
  console.log(payload.root); // Reference to the entire state tree
380
- console.log(payload.terminated); // Boolean: Did a previous mediator kill this action?
381
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?
382
403
  }); // you could use external callbacks but typed with `Payload<T, "user.age">`
383
404
  rtr.get("user.age", (value, payload) => {});
384
405
  rtr.delete("user.age", (terminated, payload) => {});
@@ -419,7 +440,7 @@ If you mutate `store.user.profile.name = "Kosi"`, the event wave travels like th
419
440
  2. **Target Phase:** `user.profile.name`
420
441
  3. **Bubble Phase:** `user.profile` ➔ `user` ➔ `*` (Root)
421
442
 
422
- *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.*
423
444
 
424
445
  #### The Event Anatomy (`REvent` type)
425
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.
@@ -435,8 +456,11 @@ rtr.on("user.profile", (e) => {
435
456
  console.log(e.oldValue); // "John" (The previous value)
436
457
  // 2. Political Routing
437
458
  console.log(e.eventPhase); // 3 (Bubbling Phase)
438
- 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?)
439
462
  // 3. Misc
463
+ console.log(e.timestamp); // 1697059200000 (`DOMHighResTimeStamp`, configurable via `eventTimestamp` option)
440
464
  console.log(e.composedPath()); // ["Kosi", { name: "Kosi", age: 26 }, { profile: { name: "Kosi", age: 26 } }, { user: { profile: { name: "Kosi", age: 26 } } }] (refs, target -> root)
441
465
  }); // you could use external callbacks but typed with `REvent<T, "user.age">`
442
466
  ```
@@ -468,7 +492,7 @@ To help you instantly differentiate between the object *itself* being replaced,
468
492
  * If `store.user.profile = {}` happens, the listener receives `e.type === "set"`.
469
493
  * If `store.user.profile.name = "Kosi"` happens, the parent listener receives `e.type === "update"`.
470
494
 
471
- 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:
472
496
 
473
497
  ```javascript
474
498
  rtr.on("todos", (e) => console.log(e), { depth : 1 }); // only sees updates on direct children
@@ -487,13 +511,128 @@ rtr.on("todos", (e: REvent<User, "todos", 1>) => {
487
511
  const { path, key } = e.target;
488
512
  console.log(path, key); // or e.target.path, e.target.key
489
513
  }
490
- }, { depth: 1 }); // you need the generic for external callbacks only
514
+ }, { depth: 1 }); // REvent generic is used for external callbacks
491
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).*
492
517
 
493
518
  ---
494
519
 
495
520
  ## Architectural Tricks
496
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
+
497
636
  ### The CSS Black Box
498
637
 
499
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-0TjDsae1.cjs';
2
- import { m as TimeTravelModule } from './timeTravel-wU23uudD.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-0TjDsae1.js';
2
- import { m as TimeTravelModule } from './timeTravel-CX100S8f.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 };