# @lensmcp/valtio-instrumentation

Drop-in instrumented Valtio for LensMCP — per-path subscription and mutation tracking.

`@lensmcp/valtio-instrumentation` is a transparent, drop-in replacement for [`valtio`](https://github.com/pmndrs/valtio). It exposes the exact same surface (`proxy`, `useSnapshot`, `snapshot`, `subscribe`, `ref`, …), but the `proxy` and `useSnapshot` you get are instrumented: every store creation, every state mutation, and every per-path component subscription is observed and published as a LensMCP event. This is what lets the [LensMCP](https://github.com/kiwiapps-ltd/lensmcp) lens show frontend state changes inside a flow/trace — which store changed, on which path, and which mounted components were subscribed to it at the time.

The instrumentation is path-aware. `proxy()` registers each store and subscribes at the root with Valtio's path-tracking ops enabled, emitting one `state-update` per touched leaf. `useSnapshot()` inlines Valtio v2's `proxy-compare` "affected" map so it knows exactly which `(storeId, path)` pairs a component actually read, and registers one subscription per path instead of a single store-wide one. You normally don't import this package by hand — [`@lensmcp/vite-plugin`](https://github.com/kiwiapps-ltd/lensmcp) aliases `valtio → @lensmcp/valtio-instrumentation` at dev time, so your existing Valtio code is observed with no source changes.

## Install

```bash
yarn add @lensmcp/valtio-instrumentation
```

In a LensMCP-enabled Vite app you usually don't need this: `@lensmcp/vite-plugin` aliases `valtio` to this package automatically, so `import { proxy, useSnapshot } from 'valtio'` resolves here in dev. Install it directly only if you want to import `@lensmcp/valtio-instrumentation` explicitly or use the LensMCP-specific store-registry exports.

`react` (`^18 || ^19`) and `valtio` (`^1 || ^2`) are peer dependencies, and it depends on `@lensmcp/react-instrumentation` for event publishing and flow context.

## Usage

Because the surface matches Valtio exactly, existing code works unchanged — it's just now observed:

```tsx
// With @lensmcp/vite-plugin, this import resolves to @lensmcp/valtio-instrumentation in dev.
import { proxy, useSnapshot } from 'valtio';

// `proxy()` registers the store and emits a `store-created` event.
const state = proxy({ count: 0, user: { name: 'Ada' } });

function Counter() {
  // `useSnapshot()` infers the paths this component reads (here: `count`)
  // and registers one subscription per path.
  const snap = useSnapshot(state);
  return <button onClick={() => state.count++}>{snap.count}</button>;
}
```

Mutating `state.count` emits a `state-update` event for path `count`, tagged with the components currently subscribed to that path (and its parents). No callbacks, providers, or annotations are required beyond mounting your tree under `LensmcpRoot` from `@lensmcp/react-instrumentation`.

If you import this package directly, you can pass LensMCP options to `proxy()`:

```ts
import { proxy } from '@lensmcp/valtio-instrumentation';

const cart = proxy(
  { items: [] },
  { storeId: 'cart', source: 'src/cart.ts:12', emitInitialPreview: true },
);
```

For cases where you want to declare a read path explicitly rather than infer it, use `useTrackedSnapshot`:

```ts
import { useTrackedSnapshot } from '@lensmcp/valtio-instrumentation';

const snap = useTrackedSnapshot(cart, { storeId: 'cart', path: 'items' });
```

## API

### Valtio-compatible surface

| Export | Description |
| --- | --- |
| `proxy(initial, opts?)` | Instrumented `proxy`. Registers the store, emits `store-created`, and emits a `state-update` per mutated path. Accepts `LensmcpProxyOptions`. |
| `useSnapshot(target)` | Instrumented `useSnapshot` with the same signature as Valtio's. Infers read paths via `proxy-compare` and registers one subscription per path. |
| `useTrackedSnapshot(target, opts)` | Like `useSnapshot`, but the caller declares `{ storeId, path }` explicitly instead of inferring it. Accepts `UseSnapshotOptions`. |
| `snapshot` | Re-exported from `valtio/vanilla` (uninstrumented). |
| `subscribe` | Re-exported from `valtio/vanilla`. |
| `ref` | Re-exported from `valtio/vanilla`. |
| `getVersion` | Re-exported from `valtio/vanilla`. |
| `unstable_enableOp` | Re-exported from `valtio/vanilla`. |
| `unstable_getInternalStates` | Re-exported from `valtio/vanilla`. |
| `unstable_replaceInternalFunction` | Re-exported from `valtio/vanilla`. |
| `LensmcpProxyOptions` | Options for `proxy()`: `storeId?`, `source?`, `emitInitialPreview?`. |
| `UseSnapshotOptions` | Options for `useTrackedSnapshot()`: `storeId` (required), `path?`. |

> `proxy` and `useSnapshot` are the instrumented versions; everything else passes through from the real Valtio (imported via the `valtio/vanilla` subpath so this module never aliases itself).

### Store registry (LensMCP-specific)

Module-level bookkeeping shared by `proxy`, `useSnapshot`, and the LensMCP reducer. Most apps never call these directly.

| Export | Description |
| --- | --- |
| `registerStore(storeId, source?)` | Record a store and its source location. |
| `tagProxy(proxyObject, storeId)` | Associate a proxy object with its `storeId` so `useSnapshot(store)` can recover the id with no extra args. |
| `storeIdOf(proxyObject)` | Look up the `storeId` previously tagged onto a proxy. |
| `addSubscription(entry)` | Register a `{ storeId, path, componentInstanceId }` subscription. |
| `removeSubscription(entry)` | Remove a subscription entry. |
| `subscribersFor(storeId, path)` | List `componentInstanceId`s subscribed to an exact path. |
| `subscribersForPathAndParents(storeId, path)` | List subscribers of a path unioned with every parent prefix and the root. |
| `resetRegistry()` | Clear all stores and subscriptions (useful in tests). |
| `SubscriptionEntry` | `{ storeId, path, componentInstanceId }`. |

### Event payload types

The wire-shape payloads emitted by this package (consumed by the LensMCP Valtio reducer via `event.raw.kind`):

| Export | Description |
| --- | --- |
| `ValtioPublishPayload` | Union of `store-created`, `state-update`, `subscription-added`, and `subscription-removed`. |
| `ValtioStoreRecord` | `{ storeId, source?, initialPreview? }`. |
| `ValtioUpdateRecord` | `{ storeId, path, changedKeys, beforeHash, afterHash, preview?, subscribers }`. |
| `ValtioSubscriptionRecord` | `{ storeId, path, componentInstanceId? }`. |

## How it fits

- **Aliased by [`@lensmcp/vite-plugin`](https://github.com/kiwiapps-ltd/lensmcp).** The plugin rewrites `valtio` to this package at dev time, so instrumentation is zero-touch for app code.
- **Pairs with [`@lensmcp/react-instrumentation`](https://github.com/kiwiapps-ltd/lensmcp).** This package publishes events through its `publish()` API and reads the active flow/component context from `useLensmcpContext()` / `LensmcpRoot`, so state updates are correlated with the render and effect timeline in the same trace.
- **Speaks [`@lensmcp/protocol-types`](https://github.com/kiwiapps-ltd/lensmcp).** Events ride the LensMCP publish envelope (`source: 'valtio'`, `category: 'state'`) defined by the shared protocol, so the lens and reducers can render store creation, mutations, and per-path subscriptions in a unified flow view.

---

Part of [LensMCP](https://github.com/kiwiapps-ltd/lensmcp). Apache-2.0.
