# How the Extraction Phase Captures Multi-State Component Behavior in AI Website Cloner

> Learn how the AI website cloner's extraction phase captures multi-state component behavior. It uses sequential CSS snapshots and diffing to generate exact behavior specifications.

- Repository: [JCodesMore/ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template)
- Tags: deep-dive
- Published: 2026-07-07

---

**The extraction phase captures multi-state component behavior by taking sequential CSS snapshots before and after programmatically triggering UI state changes, then diffing the computed styles to generate exact behavior specifications.**

The **AI Website Cloner Template** (`JCodesMore/ai-website-cloner-template`) automates the replication of complex web interfaces by recording precise visual specifications from target sites. During the **extraction phase**, the system handles interactive components—like buttons, modals, and navigation items—by capturing how their styles transform across different states.

## Understanding the Extraction Pipeline

According to the repository's README, the template follows an **"extract → spec → dispatch"** loop. While most extraction steps require only a single pass, multi-state component behavior capture is the only step that necessitates **two passes** of the same element. This ensures that builder agents receive exact styling data without guesswork, preserving hover effects, active states, and transitions in the final Next.js codebase.

## The Five-Step Multi-State Capture Process

The extraction workflow defined in [`.windsurf/workflows/clone-website.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/.windsurf/workflows/clone-website.md) implements a rigorous five-step methodology to record stateful interactions.

### Step 1: Initial CSS Snapshot

For a given CSS selector, the agent executes a JavaScript extraction script inside the browser via MCP (Model Context Protocol). This script walks the DOM, records each element’s computed style properties, and returns a JSON payload. The `props` array in the extraction script lists the specific CSS properties captured, including `fontSize`, `backgroundColor`, `transform`, `transition`, and `boxShadow`.

### Step 2: Triggering State Changes

After capturing the default state (State A), the agent programmatically triggers the interaction that changes the component’s appearance. The documentation explicitly instructs: "State A… then trigger the state change… State B". This might involve dispatching a `MouseEvent` for hover states, `click` events for tabs, or `scroll` events for sticky headers.

### Step 3: Secondary CSS Snapshot

The same extraction script is re-executed on the **same element** while the UI remains in the new state. This second snapshot captures the computed styles after the DOM has settled into the modified state.

### Step 4: Generating the Diff

The two JSON payloads are compared property by property. Any CSS property whose value differs between State A and State B is recorded as a **behavior specification**. For example, a hover effect might generate a diff entry showing `background-color: #fff → #f0f0f0`. The "States & Behaviors" section of the workflow file defines the format for these specifications.

### Step 5: Persisting to Specification Files

The resulting diff, along with the trigger description and any transition CSS, is written to a Markdown specification file at `docs/research/components/<Component>.spec.md`. This file serves as the contract for subsequent builder agents, ensuring they implement the exact multi-state styling logic.

## The Extraction Script Implementation

The core extraction logic lives in [`.windsurf/workflows/clone-website.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/.windsurf/workflows/clone-website.md). The script runs inside the target page and recursively walks the DOM tree up to a depth of four levels, filtering out default values like `none`, `normal`, `auto`, and `0px`.

```javascript
// run inside the page with the target selector substituted
(function (selector) {
  const el = document.querySelector(selector);
  if (!el) throw new Error('Element not found: ' + selector);

  const props = [
    'fontSize','fontWeight','fontFamily','lineHeight','letterSpacing','color',
    'textTransform','textDecoration','backgroundColor','background',
    'padding','paddingTop','paddingRight','paddingBottom','paddingLeft',
    'margin','marginTop','marginRight','marginBottom','marginLeft',
    'width','height','maxWidth','minWidth','maxHeight','minHeight',
    'display','flexDirection','justifyContent','alignItems','gap',
    'gridTemplateColumns','gridTemplateRows','borderRadius','border',
    'boxShadow','position','top','right','bottom','left','zIndex',
    'opacity','transform','transition','cursor','objectFit','filter'
  ];

  const extractStyles = el => {
    const cs = getComputedStyle(el);
    const out = {};
    props.forEach(p => {
      const v = cs[p];
      if (v && !['none','normal','auto','0px','rgba(0, 0, 0, 0)'].includes(v))
        out[p] = v;
    });
    return out;
  };

  const walk = (node, depth = 0) => {
    if (depth > 4) return null;
    const children = [...node.children];
    return {
      tag: node.tagName.toLowerCase(),
      classes: node.className?.split(' ').slice(0,5).join(' '),
      text: node.childNodes.length===1 && node.childNodes[0].nodeType===3 ?
            node.textContent.trim().slice(0,200) : null,
      styles: extractStyles(node),
      images: node.tagName==='IMG' ? {
        src: node.src, alt: node.alt,
        naturalWidth: node.naturalWidth, naturalHeight: node.naturalHeight
      } : null,
      childCount: children.length,
      children: children.slice(0,20).map(c=>walk(c,depth+1)).filter(Boolean)
    };
  };

  return JSON.stringify(walk(el,0),null,2);
})('SELECTOR');

```

## Capturing Hover States in Practice

The following pattern demonstrates how the extraction phase handles a button hover state. The agent first captures the default state, triggers the hover interaction programmatically, captures the hover state, and computes the differential.

```javascript
// 1️⃣ Capture state A (default)
let stateA = await extract('button.my-primary');

// 2️⃣ Trigger hover programmatically
document.querySelector('button.my-primary')
        .dispatchEvent(new MouseEvent('mouseover', {bubbles:true}));

// 3️⃣ Capture state B (hover)
let stateB = await extract('button.my-primary');

// 4️⃣ Produce diff (simplified)
const diff = {};
for (const prop in stateA.styles) {
  if (stateA.styles[prop] !== stateB.styles[prop]) {
    diff[prop] = {
      from: stateA.styles[prop],
      to:   stateB.styles[prop]
    };
  }
}
console.log('Hover diff', diff);

```

The resulting `diff` object is then written into the component spec under the "States & Behaviors" section, following the template format specified in the workflow file.

## Key Files and Their Roles

| File | Purpose |
|------|---------|
| [`.windsurf/workflows/clone-website.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/.windsurf/workflows/clone-website.md) | Contains the full extraction script and multi-state capture instructions at lines 34-41 and 82-88. |
| [`README.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/README.md) | Describes the high-level extraction → spec → dispatch pipeline at lines 86-95. |
| `docs/research/components/*.spec.md` | Storage location for captured multi-state data used by builder agents. |
| [`src/app/page.tsx`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/page.tsx) | Entry point where cloned pages are assembled from specification files. |

## Summary

- **Multi-state behavior** is captured by taking two CSS snapshots: one before and one after triggering a state change.
- The **extraction script** runs via browser MCP and filters out default CSS values to produce clean JSON representations.
- **Diff generation** compares the two snapshots and records only changed properties, creating precise behavior specifications.
- Specifications are stored in `docs/research/components/<Component>.spec.md` to serve as contracts for builder agents.
- This approach ensures that **interactive states** (hover, active, focus) are faithfully reproduced in the generated Next.js codebase without approximation.

## Frequently Asked Questions

### How does the extraction phase handle complex interactions like scrolling or focus states?

The extraction phase treats any DOM mutation as a state change. For scroll-based states, the agent scrolls the container to the target position before taking the second snapshot. For focus states, it calls `.focus()` on the element. The same diff-generation logic applies regardless of the interaction type, capturing the exact computed styles after the DOM update settles.

### What CSS properties are captured during the extraction phase?

The extraction script captures 48 specific properties including typography (`fontSize`, `letterSpacing`), layout (`display`, `flexDirection`, `gridTemplateColumns`), visual styling (`backgroundColor`, `borderRadius`, `boxShadow`), and interactive properties (`transition`, `transform`, `cursor`). The script filters out default values like `none`, `auto`, and `rgba(0, 0, 0, 0)` to keep the output concise.

### Where is the captured multi-state data stored after extraction?

The diff data and trigger descriptions are written to Markdown specification files at `docs/research/components/<Component>.spec.md`. These files follow the "States & Behaviors" template defined in the workflow documentation and serve as the single source of truth for builder agents that generate the actual React components.

### Why does the extraction phase require two passes for multi-state components?

Two passes are required because **computed styles** in the browser only reflect the current state of the DOM. After triggering an interaction (like hover), the element's CSS values change in real-time. By capturing State A and State B separately, the system can calculate the exact difference between states, ensuring that builder agents implement the correct transition values rather than inferring them from static inspection.