# How Snapshot Refs (@N) Work in ego-lite: Validity and Lifecycle Rules

> Learn how egol-lite snapshot refs (@N) work. Discover their validity and lifecycle rules, including expiration after navigation, DOM mutations, or a 2-second TTL.

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

---

**Snapshot refs (@N) are short-lived numeric handles that point to DOM elements captured in the current semantic page snapshot and become invalid after navigation, DOM mutations, or a 2-second TTL expiration.**

In the **citrolabs/ego-lite** browser automation framework, snapshot refs provide a temporary way to reference specific DOM nodes without relying on stable CSS selectors or XPath expressions. These refs are generated when you call the `snapshot()` helper and are maintained in a transient map that the runtime automatically invalidates under specific conditions.

## What Are Snapshot Refs (@N)?

A **snapshot ref** is written as `@N`, where `N` represents a numeric identifier assigned to an element during the most recent semantic page snapshot. When the runtime captures a snapshot, it records a mapping between these numeric identifiers and the underlying Chrome DevTools Protocol (**CDP**) `backendNodeId`s.

According to the source code in [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts), the **RefMap** class stores each entry as an object containing the backend node ID, accessibility role, element name, index position, frame ID, and selector information:

```typescript
this.map.set(refId, {
  backendNodeId,
  role,
  name,
  nth,
  selector: undefined,
  frameId,
});

```

The helper function `parseRef()` located at lines 43-55 in [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts) recognizes ref strings written as `@123` (or `ref=123`) and extracts the numeric identifier so the resolver can perform the lookup.

## How Snapshot Refs Are Created and Stored

Snapshot refs originate from the semantic snapshot mechanism in [`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts). When a script invokes the `snapshot()` helper, the browser runtime creates a fresh **RefMap** instance that associates numeric IDs with live DOM nodes.

The resolution process works as follows:

1. The script requests an element using a ref like `@7`
2. `parseRef()` in [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts) extracts the numeric string `"7"`
3. The **ElementResolver** ([`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)) looks up this ID in the current RefMap
4. If found, the resolver returns the corresponding DOM element; if not, it triggers a re-snapshot or throws a transient error

Because refs depend on the current snapshot state, they are **tied to a specific point in time** and are not persistent across page changes.

## When Snapshot Refs Become Invalid

Refs remain valid only while the snapshot map remains fresh. The ego-lite runtime clears or refreshes the map in several specific situations:

### Navigation Events

When a new page loads or the current URL changes, the snapshot is **discarded immediately**. All previous refs are removed from memory because they pointed to nodes in the previous document.

### DOM Mutations

Significant structural changes—such as when an element is removed or its position in the DOM changes—trigger a new snapshot. The old ref no longer points to a live node and is removed from the map.

### TTL Expiration

The runtime automatically discards the snapshot after **2 seconds of inactivity**. This timeout is implemented in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) to prevent stale references from accumulating during long-running scripts.

### Explicit Clearing

Calling `clearPreferredTarget()` or similar helper functions that reset the snapshot state will **immediately clear** all stored refs from the RefMap.

### Failed Resolution Attempts

When code attempts to resolve a ref that is not present in the current map, the runtime treats this as a **transient failure** and triggers an automatic re-snapshot. The original ref is considered expired, and the operation retries with a fresh snapshot.

## Best Practices for Using Snapshot Refs

Snapshot refs are **intended for short-term use only**, specifically within a single helper call or a quick series of actions on the same page state. For longer-lived interactions, you should avoid refs in favor of stable targeting strategies.

Use **stable locators** such as `css=…`, `xpath=…`, or `loc=…` for any persistent element references. These selectors query the DOM live and never suffer from snapshot invalidation.

## Working with Snapshot Refs: Code Examples

The following example demonstrates capturing a snapshot and using a ref before it expires:

```javascript
const { snapshot, click } = ego;

// Take a snapshot and capture a ref
const snap = await snapshot();  // Creates a fresh RefMap
const btnRef = snap.refs.find(r => r.name === 'submit');  // e.g., "@7"

// Use the ref immediately while the snapshot is fresh
await click(btnRef);  // Resolves @7 to the element and clicks it

```

After navigation or a 2-second delay, the same ref becomes invalid:

```javascript
// Navigation occurs here...

// This will fail because the snapshot was cleared
await click(btnRef);  
// Throws: ElementResolutionError: Unknown ref: 7 (transient)

```

For long-running scripts, prefer stable locators that query the DOM directly:

```javascript
// This locator remains valid regardless of snapshot state
await click('css=button[type="submit"]');

```

## Key Source Files

The snapshot ref lifecycle is implemented across four core files in the citrolabs/ego-lite repository:

- **[`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts)** – Defines the **RefMap** class and the `parseRef()` parser that converts `@N` strings to numeric identifiers.
- **[`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts)** – Tracks snapshot freshness and manages when the ref map is cleared or refreshed.
- **[`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)** – Resolves selector strings (including refs) to actual DOM elements using the current RefMap.
- **[`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts)** – Documents the accepted selector syntax, including the `@N` format for snapshot refs.

## Summary

- **Snapshot refs (@N)** are temporary numeric handles tied to a specific semantic page snapshot captured by the ego-lite runtime.
- They are stored in **RefMap** ([`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts)) and resolved via `parseRef()` and the **ElementResolver**.
- Refs become invalid after **navigation**, **DOM mutations**, **2 seconds of inactivity (TTL)**, or **explicit clearing**.
- Failed ref lookups trigger automatic re-snapshotting, marking the original ref as transient.
- For automation scripts, use **stable locators** (`css=…`, `xpath=…`) instead of refs for any element interactions that persist beyond immediate snapshot use.

## Frequently Asked Questions

### How long do snapshot refs (@N) remain valid?

Snapshot refs remain valid for approximately **2 seconds** after creation or until the next navigation or DOM mutation occurs. The runtime enforces a TTL (time-to-live) expiration of 2 seconds of inactivity, after which the RefMap is automatically cleared.

### What happens if I use a snapshot ref after the page changes?

If you attempt to use a snapshot ref after navigation or significant DOM changes, the **ElementResolver** will throw an `ElementResolutionError` indicating the ref is unknown or transient. The runtime may automatically attempt a re-snapshot, but the original numeric handle will not resolve to the intended element.

### Should I use @N refs or CSS selectors for element targeting?

Use **@N refs** only for short-term, immediate actions within the same snapshot context. For any long-running scripts or persistent element references, prefer **CSS selectors**, XPath expressions, or named locators (`loc=…`) because they query the DOM live and do not expire when the snapshot clears.

### Where is the snapshot ref data stored in the ego-lite source code?

Snapshot ref data is stored in the **RefMap** class defined in [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts). This class maintains a mapping between numeric ref IDs and CDP `backendNodeId`s. The freshness of this data is managed by [`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts), while resolution logic resides in [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts).