# Module inspection

Observe detached ownership snapshots and bounded activity traces, with an optional development iframe bridge.



## Ownership snapshots [#ownership-snapshots]

```ts
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 [#activity-sampling]

```ts
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 [#same-origin-preview-bridge]

```ts
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.
