Module inspection
Observe detached ownership snapshots and bounded activity traces, with an optional development iframe bridge.
Ownership snapshots
const snapshot = host.inspect()
const stop = host.subscribe(() => console.log(host.inspect()))
// Stop observing when the inspector closes.
stop()
inspect() returns detached, JSON-safe ModuleSnapshot[]. Each snapshot contains
id, constructor name, optional string label (from the module's id property),
status, inherited priority, selection count, declared capabilities, and
children. Node IDs are stable within a host; constructor names may be minified
in production. Inspection does not execute debug() or expose selected values.
subscribe(listener) runs after root mount, unmount, and retirement. It does not
run immediately, on state changes, or on frames. Failed mounts do not publish a
tree. Retirement publishes an empty tree and releases ownership subscribers.
Activity sampling
import type { ModuleActivity } from '@joycostudio/xyz'
const samples: ModuleActivity[] = []
const stop = host.subscribeActivity((sample) => {
samples.push(sample)
if (samples.length > 10) samples.shift()
})
// Releases the recorder and timer when the last observer disconnects.
stop()
Activity recording is opt-in and disabled when NODE_ENV is production. One
timer per host delivers a sample every second to all observers. No recorder or
timer is allocated without a subscriber. With no process global, explicit
activity subscriptions still work; unbundled imports do not require a Node shim.
Each sample has:
| Field | Contents |
|---|---|
sequence | Increasing sample number for the current recording session. |
elapsed | Milliseconds since the previous sample or recorder creation. |
counts | Per-module counts of tick, emit, receive, selection, and sync. |
entries | At most 200 event/state trace entries per interval. |
dropped | Number of older entries dropped when the interval exceeded its bound. |
connections | Current listener edges with source, target, event, and count. External endpoints are null. |
Entries include a timestamp, module and peer IDs, kind, name, bounded text detail, and causal entry IDs. They follow synchronous emission through reception, store notification, selection changes, and coalesced sync. Tick activity increments counters without producing trace rows. Counts and entries reset after each sample; retain bounded history in your inspector if desired.
Payload previews do not retain live state or invoke ordinary getters or
toJSON(). Object change previews examine at most 64 enumerated keys per level
and fall back to a short value preview when the scan is truncated. This bounds
comparisons and retained keys; JavaScript key enumeration and custom proxy traps
can still do additional work.
Same-origin preview bridge
import { publishModuleTree } from '@joycostudio/xyz'
const disconnect = publishModuleTree(host, 'Scene preview')
// On preview cleanup:
disconnect()
The bridge is active only in NODE_ENV=development inside an iframe. It publishes
the initial tree and later ownership changes to the same-origin parent. Outside
that environment, it returns a no-op cleanup. It accepts requests only from that
parent window with the matching origin.
When crypto.randomUUID() is unavailable (such as plain HTTP on a LAN address),
the bridge uses a timestamp/random session identifier. Session IDs correlate
messages; origin and source checks enforce the connection boundary.
Build a parent inspector with the exported constants and TreeMessage union:
| Constant | Message |
|---|---|
TREE_REQUEST | Parent sends { type: TREE_REQUEST } to request a snapshot and start activity. |
TREE_SNAPSHOT | Preview replies { type: TREE_SNAPSHOT, tree }, where tree has session, name, and roots; cleanup sends tree: null. |
TREE_ACTIVITY | Preview sends { type: TREE_ACTIVITY, session, activity } once per second while requested. |
TREE_RELEASE | Parent sends { type: TREE_RELEASE } to stop activity without removing the preview. |
Parent consumers should check both event.origin and
event.source === iframe.contentWindow, and associate activity with the current
tree's session. Use the constants on both sides: the package's protocol is
namespaced xyz:module-tree:*, replacing the source app's bzrp:module-tree:*.
The package provides inspection data and transport. Inspector UI, graph layout, source-file discovery, and application-specific Studio integration remain in the consuming app.