Tuning what gets traced
A busy app can update signals hundreds of times per frame. @sigx/devtools gives you a
few knobs to keep the panel responsive and to choose exactly which event families flow
over the wire.
Throttling reactive updates
The plugin coalesces reactive:updated events per primitive id within a millisecond
window. The default is 16 (~one frame), which collapses bursts of same-id updates into
a single event. Raise it to cap traffic harder, or set 0 to disable coalescing and see
every update:
import { devtools } from '@sigx/devtools';
// ~10 updates/sec per primitive at most
app.use(devtools({ throttleMs: 100 }));
// every update, no coalescing
app.use(devtools({ throttleMs: 0 }));
Throttling only affects reactive:updated — create and dispose events are always sent.
Dropping reactivity entirely
If you only care about the component tree, props, stores, and router, turn reactivity off. This keeps those timelines but drops all signal/computed/effect events:
app.use(devtools({ includeReactivity: false }));
Observing stores
Stores are observed automatically: the plugin subscribes to wildcard patterns on the
core inspection registry (@sigx/runtime-core/inspect), where every @sigx/store
instance registers its state, action, and custom-event topics. Stores that already
exist and stores created later are both picked up — no per-store wiring. The
includeStores option (default true) turns this off:
import { defineApp } from 'sigx';
import { devtools } from '@sigx/devtools';
import { App } from './App';
defineApp(<App />)
.use(devtools({
includeStores: false, // drop store:action / store:mutation / store:event
}))
.mount('#app');
Each action dispatch is reported in three phases that share one actionId:
dispatching then either dispatched (with the serialized result and durationMs)
or failed (with a serialized error). Mutations carry the changed key and the new
value serialized inline. Store custom events (defineEvents) arrive as store:event
with the event key and serialized payload.
Note that observing a store activates its refCount state watchers — state observation is pay-when-observed by design, so the watchers run only while devtools (or another subscriber) is attached.
Observing the router
Pass your router instance to stream navigations as router:nav events:
import { defineApp } from 'sigx';
import { devtools } from '@sigx/devtools';
import { createRouter } from '@sigx/router';
import { App } from './App';
const router = createRouter({ routes: [/* ... */] });
defineApp(<App />)
.use(router)
.use(devtools({ router }))
.mount('#app');
The observer wraps the router's reactive currentRoute in an effect. The route present
at mount is skipped — only subsequent navigations fire. Each event carries fromPath,
toPath, and serialized params and query.
How values are serialized
The plugin never puts live reactive proxies or DOM nodes on the wire. Two mechanisms keep traffic safe and small:
- Value references. Component props and other large values are sent as a numeric
ValueRef. The panel resolves the actual value on demand with aget:valuerequest. Object values are held viaWeakRefso unmounted components can be garbage-collected; primitives and functions are held strongly. - Bounded serialization. When a value is serialized it is unwrapped from any reactive
proxy first, cycles become
{ kind: 'circular' }, functions become{ kind: 'function', name }, and recursion is bounded to a depth of 4 — past which a value becomes{ kind: 'truncated', reason: 'depth' }. Objects/arrays are capped at 100 entries, but extra entries past the cap are silently dropped with notruncatedmarker. Only own enumerable string keys are included; symbol keys (framework internals) are skipped.
Reactive current values are resolved with get:reactive-value, which serializes through
toRaw so reading the value does not re-engage the proxy or create accidental
subscriptions.
You don't call the serializer yourself — it runs inside the plugin and observers — but
knowing its bounds explains why a deeply nested object shows up truncated in the panel.
A combined configuration
import { defineApp } from 'sigx';
import { devtools } from '@sigx/devtools';
import { createRouter } from '@sigx/router';
import { App } from './App';
const router = createRouter({ routes: [/* ... */] });
defineApp(<App />)
.use(router)
.use(devtools({
appName: 'shop',
includeStores: true, // the default — stores auto-discovered
router,
includeReactivity: true,
throttleMs: 32,
}))
.mount('#app');
See the API Reference for every option and event shape.
