# How the Ref-Map Gets Rebuilt on Snapshots to Keep @N References Valid in ego-lite

> Learn how the ref-map rebuilds on snapshots in ego-lite to keep @N references valid. Discover the process of clearing and repopulating mappings for accurate DOM node references.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: internals
- Published: 2026-08-07

---

**The ref-map is rebuilt after every snapshot by clearing the existing `RefMap` and repopulating it with fresh `@N` to `backendNodeId` mappings from the latest snapshot data, ensuring references always point to current DOM nodes.**

In the `citrolabs/ego-lite` browser automation framework, `@N` references provide short-lived identifiers to DOM elements. When the page state changes, these references must remain accurate. The framework achieves this by automatically rebuilding the global ref-map each time a new snapshot is captured.

## The Three-Step Ref-Map Rebuild Process

When `ego-lite` captures a page snapshot, it orchestrates a coordinated rebuild of the reference map through three distinct phases.

### Step 1: Capture the Raw Snapshot

The process begins in [`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts), where the `snapshotRaw()` function invokes the underlying ego runtime via `ego.snapshot`. This returns a structured snapshot object containing a `refs` array. Each entry in this array maps a snapshot-generated reference number to its corresponding `backendNodeId`, along with metadata including role, name, and optional `frameId`.

### Step 2: Register the Refresh Callback

Immediately after capturing the snapshot, [`observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/observe.ts) registers a callback using `registerSnapshotForRefRefresh(() => snapshotRaw())`. This callback is stored in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) and executes automatically after every successful snapshot operation.

### Step 3: Clear and Repopulate the RefMap

The registered callback triggers `refreshRefs()`, implemented in [`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts). This function performs the actual rebuild:

- It calls `browserRefMap.clear()` to wipe the previous mappings (implemented in [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts) as `RefMap.clear()`)
- It iterates over the `refs` array from the latest snapshot
- For each reference, it invokes `browserRefMap.addWithFrame(refId, backendNodeId, role, name, nth, frameId)` to store a fresh mapping from the textual `@N` identifier to the current `backendNodeId` and frame context

## Runtime Resolution and Automatic Recovery

When automation scripts invoke helpers like `click("@12")` or `js("@5")`, the system resolves these identifiers through `RefMap.get()`. If the map lacks the requested reference, [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) automatically triggers a new snapshot. This ensures the ref-map is always current before resolving element queries.

```typescript
// Taking a snapshot automatically rebuilds the ref-map
const snap = await page.snapshot();  // Contains refs like "@3", "@7"...

// Using a reference triggers lookup against the fresh map
await page.click("@3");  // Resolves via RefMap.get("@3")

```

## Manual Ref-Map Refresh

While the system handles ref-map rebuilds automatically, you can force a manual refresh when necessary:

```typescript
import { refreshRefs } from "ego-browser/src/ref-state.js";

// Forces a fresh snapshot and complete ref-map rebuild
await refreshRefs();

```

## Summary

- **Snapshot-driven rebuilds**: Every call to `snapshotRaw()` in [`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts) triggers a complete ref-map refresh through the registered callback mechanism
- **Complete replacement**: The `RefMap` is cleared entirely and rebuilt from the `refs` array rather than incrementally updated
- **Backend node mapping**: Each `@N` reference maps to a `backendNodeId` stored with optional frame context via `addWithFrame()`
- **Automatic recovery**: [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) detects stale or missing references and initiates new snapshots automatically
- **Key files**: [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts) defines the map structure, [`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts) manages the singleton instance, and [`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts) orchestrates the snapshot cycle

## Frequently Asked Questions

### What triggers a ref-map rebuild in ego-lite?

A rebuild triggers automatically after every successful snapshot. The `registerSnapshotForRefRefresh()` callback in [`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts) executes `refreshRefs()`, which clears and repopulates the map from the latest snapshot data.

### Why does the ref-map get cleared instead of updated incrementally?

The `browserRefMap.clear()` approach ensures consistency. Since snapshots capture the complete DOM state, incremental updates risk leaving stale entries. A full rebuild guarantees that every `@N` reference points to a node that actually exists in the current snapshot.

### How does ego-lite handle invalid or expired @N references?

When [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) encounters a reference not present in the current map, it automatically triggers a fresh snapshot. This rebuilds the ref-map with current data, allowing the reference to resolve against the new DOM state.

### Can I use @N references across page navigations?

No, `@N` references are short-lived identifiers tied to a specific snapshot. After navigation, you must capture a new snapshot to generate valid references for the new page state.