<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Alechko</title>
    <description>The latest articles on Forem by Alechko (@alexey_sokolov_10deecd763).</description>
    <link>https://forem.com/alexey_sokolov_10deecd763</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3462436%2F2315a774-ee7c-40c5-8a1f-b0c2f6360a60.jpg</url>
      <title>Forem: Alechko</title>
      <link>https://forem.com/alexey_sokolov_10deecd763</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alexey_sokolov_10deecd763"/>
    <language>en</language>
    <item>
      <title>🧩 Runtime Snapshots #15 — Your AI Agent Is Blind. We're Fixing That.</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Tue, 17 Mar 2026 11:01:10 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-15-your-ai-agent-is-blind-were-fixing-that-4166</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-15-your-ai-agent-is-blind-were-fixing-that-4166</guid>
      <description>&lt;p&gt;Your AI agent can write code, analyze data, summarize documents, and debate philosophy.&lt;/p&gt;

&lt;p&gt;It cannot look at a web page.&lt;/p&gt;

&lt;p&gt;Not really. Not the way you do when you open a browser tab and &lt;em&gt;see&lt;/em&gt; what's there — the layout, the buttons, the form that's half-loaded, the modal blocking the CTA.&lt;/p&gt;

&lt;p&gt;Claude, ChatGPT, Cursor, Gemini — they're powerful. And in the browser, they're blind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three ways we've tried to give AI sight. All broken.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Screenshots.&lt;/strong&gt;&lt;br&gt;
The most common workaround. Take a screenshot, paste it into the chat. The AI "sees" pixels. But pixels have no element IDs, no computed styles, no z-index, no ARIA roles. The AI can't tell you &lt;em&gt;which&lt;/em&gt; button is covered — just that something looks off. And vision tokens aren't cheap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw HTML.&lt;/strong&gt;&lt;br&gt;
Dump the page source. 2MB of scripts, nav menus, analytics tags, third-party widgets. The context window fills up before the AI reads anything useful. The signal is buried under 600K tokens of noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility trees.&lt;/strong&gt;&lt;br&gt;
Better in theory. Structured, semantic. But AXTrees miss computed styles, miss positions, miss z-index stacks. What assistive tech perceives is not what gets rendered. In benchmarks, AXTree-based agents hit about 60% task accuracy.&lt;/p&gt;

&lt;p&gt;None of these give AI what it needs: a structured, token-efficient representation of what the user actually sees.&lt;/p&gt;


&lt;h2&gt;
  
  
  What we've been building
&lt;/h2&gt;

&lt;p&gt;If you've followed this series, you know about &lt;a href="https://dev.to/alexey_sokolov_10deecd763"&gt;SiFR&lt;/a&gt; — Structured Interaction Format. A semantic representation of the live DOM that scores every element by importance, tags actions (&lt;code&gt;[clickable]&lt;/code&gt;, &lt;code&gt;[fillable]&lt;/code&gt;, &lt;code&gt;[hoverable]&lt;/code&gt;), maps spatial relationships, and fits in 5–15KB instead of 200–400KB.&lt;/p&gt;

&lt;p&gt;For the past months, SiFR lived inside a browser extension. You click, it captures, you paste the JSON into whatever LLM you use. Manual. Real active users. Zero marketing budget. People find it, use it, don't uninstall it.&lt;/p&gt;

&lt;p&gt;That part works. But there's a ceiling.&lt;/p&gt;

&lt;p&gt;Copy-paste is friction. The AI can see a snapshot — but only when you hand it one. It can't ask for another angle. It can't say "now scroll down and show me the footer." It can't click the button and see what happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We're removing that ceiling.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  AI gets eyes. And hands.
&lt;/h2&gt;

&lt;p&gt;We're building an MCP Server that connects AI tools directly to your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; is the open standard that Claude, ChatGPT, Cursor, and others use to connect to external tools. Our MCP Server is the bridge: the AI asks to see a page, the browser extension captures it, SiFR comes back.&lt;/p&gt;

&lt;p&gt;But it's not just capture anymore.&lt;/p&gt;

&lt;p&gt;The AI will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;See the page&lt;/strong&gt; — structured SiFR snapshot, importance-ranked, 10–50x smaller than raw HTML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find elements&lt;/strong&gt; — "show me all buttons on this form" returns IDs, labels, states, positions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click&lt;/strong&gt; — the extension clicks, the AI sees the result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fill forms&lt;/strong&gt; — type values, submit, confirm what changed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scroll&lt;/strong&gt; — navigate the page, capture new state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect changes&lt;/strong&gt; — "what's different after that click?" → SiFR diff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your AI agent stops guessing. It &lt;em&gt;sees&lt;/em&gt;. And now it &lt;em&gt;acts&lt;/em&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;The era of AI agents is here. Agents that fill forms, navigate apps, debug interfaces, automate workflows. They all need one thing: accurate perception of the UI.&lt;/p&gt;

&lt;p&gt;The current approach — raw HTML dumps, pixel screenshots, brittle CSS selectors — doesn't scale. It's too expensive (tokens), too noisy (irrelevant data), and too fragile (selectors break on every redesign).&lt;/p&gt;

&lt;p&gt;SiFR through MCP is a different architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:  AI → raw HTML (200-400KB) → guess → hallucinate selectors
After:   AI → SiFR (5-15KB) → see → act on what's actually there
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension does the heavy lifting in your browser. The MCP Server routes. The AI gets structured, actionable context.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The MCP Server is in testing. The extension is live — &lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;, &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/element-to-llm/" rel="noopener noreferrer"&gt;Firefox&lt;/a&gt;, and every Chromium browser.&lt;/p&gt;

&lt;p&gt;When MCP launches, I'll post the full walkthrough here.&lt;/p&gt;

&lt;p&gt;Follow if you want the announcement. Star the &lt;a href="https://github.com/e2llm/awesome-e2llm-prompts" rel="noopener noreferrer"&gt;prompts repo&lt;/a&gt; if you want to start experimenting now — we're adding MCP-specific prompt templates.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Your tests check if code works. This checks if users can use it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://e2llm.com" rel="noopener noreferrer"&gt;e2llm.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part 15 of the &lt;a href="https://dev.to/alexey_sokolov_10deecd763"&gt;Runtime Snapshots&lt;/a&gt; series — exploring how structured browser data changes the way we build, test, and ship software.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>mcp</category>
      <category>productivity</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #14: From Clipboard to Pipeline</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Wed, 11 Mar 2026 08:15:17 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-14-from-clipboard-to-pipeline-444e</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-14-from-clipboard-to-pipeline-444e</guid>
      <description>&lt;p&gt;&lt;em&gt;Previously in this series: we captured runtime DOM state as structured JSON and piped it into LLMs. Every snapshot lived in your clipboard — useful, but ephemeral. You'd capture, paste, done. No history, no comparison, no automation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;v2.8.0 changes that.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's New: Save to File
&lt;/h2&gt;

&lt;p&gt;There's now a toggle in the popup: &lt;strong&gt;Clipboard&lt;/strong&gt; or &lt;strong&gt;File&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you pick File, the snapshot lands in &lt;code&gt;Downloads/sifr-captures/&lt;/code&gt; with a filename like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;sifr-vvvvvvv_com&lt;/span&gt;&lt;span class="mi"&gt;-20260310-151338&lt;/span&gt;&lt;span class="err"&gt;.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Format: &lt;code&gt;sifr-{hostname}-{YYYYMMDD}-{HHmmss}.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's it. No config required. The directory is created automatically on first capture.&lt;/p&gt;

&lt;p&gt;Under the hood it's SiFR v2 JSON — the same structured, salience-weighted format you already know. Nothing changes about what gets captured. You just stop losing it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters: The Pipeline Unlock
&lt;/h2&gt;

&lt;p&gt;Clipboard mode treats snapshots as messages. File mode treats them as &lt;strong&gt;records&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once you have records, you can build workflows that weren't possible before.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. LLM batch processing
&lt;/h3&gt;

&lt;p&gt;You've been capturing five different states of a UI as you test it. Now you can feed them all to an LLM in sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;snapshots&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Downloads/sifr-captures/sifr-myapp-*.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analyze this UI state for accessibility issues:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No copy-paste loop. No lost context between captures.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Build a UI history log
&lt;/h3&gt;

&lt;p&gt;If you're working on a complex multi-step flow (wizard, form, checkout), capture a snapshot at each step. You now have a complete record of the UI journey:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sifr-app-20260310-140001.json  ← step 1: landing
sifr-app-20260310-140045.json  ← step 2: form filled
sifr-app-20260310-140112.json  ← step 3: confirmation
sifr-app-20260310-140130.json  ← step 4: success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed the whole sequence to an LLM with a single prompt: &lt;em&gt;"Here are 4 snapshots of a checkout flow. Find UX friction points across the journey."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The SiFR v2 Format: A Quick Refresher
&lt;/h2&gt;

&lt;p&gt;For those new to the series — SiFR v2 is E2LLM's structured DOM representation. It's not raw HTML, not a screenshot, not a DOM dump. It's a semantic compression:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~300KB&lt;/strong&gt; typical output vs 2–3MB raw DOM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Salience scoring&lt;/strong&gt; — nodes ranked high/med/low by visual and functional prominence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spatial clusters&lt;/strong&gt; — page regions with detected roles (nav, form, content, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action markers&lt;/strong&gt; — clickable, fillable, hoverable nodes explicitly flagged&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationship graph&lt;/strong&gt; — alignment, containment, proximity between elements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what makes file-based workflows practical. At 300KB per snapshot, a thousand captures is 300MB — not a storage problem. At 2–3MB per raw DOM export, the same thing becomes unwieldy fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Chrome:&lt;/strong&gt; &lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj" rel="noopener noreferrer"&gt;E2LLM on Chrome Web Store&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Firefox:&lt;/strong&gt; &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/element-to-llm/" rel="noopener noreferrer"&gt;E2LLM on Firefox Add-ons&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Prompts &amp;amp; examples:&lt;/strong&gt; &lt;a href="https://github.com/e2llm/awesome-e2llm-prompts" rel="noopener noreferrer"&gt;Here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Toggle &lt;strong&gt;File&lt;/strong&gt; mode in the popup. The &lt;code&gt;sifr-captures/&lt;/code&gt; folder will appear in your Downloads after the first capture.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Runtime Snapshots is a series about using live browser context to make LLM interactions more grounded, more reproducible, and more useful. Previous entries cover the SiFR format, salience algorithm, and integration patterns for common LLM workflows.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Issues, ideas, and pipeline experiments welcome on GitHub.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>llm</category>
      <category>devtools</category>
      <category>javascript</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #13 — The Scalpel and the Swiss Army Knife: 50k Tokens Before You Say Hello</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Mon, 09 Mar 2026 09:40:51 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-13-the-scalpel-and-the-swiss-army-knife-50k-tokens-before-you-say-hello-2ilo</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-13-the-scalpel-and-the-swiss-army-knife-50k-tokens-before-you-say-hello-2ilo</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; MCP burns 45k+ tokens on tool descriptions before your first prompt. E2LLM = 0 tokens until you paste the UI snapshot. For CSS debugging — scalpel, not Swiss Army knife.&lt;/p&gt;

&lt;p&gt;Runtime Snapshots is a series about what happens when you give LLMs the actual runtime state of a UI — not the HTML source, not a screenshot, not a description. &lt;a href="https://github.com/e2llm/awesome-e2llm-prompts" rel="noopener noreferrer"&gt;Start from #1&lt;/a&gt; or jump in here.&lt;/p&gt;

&lt;p&gt;We covered the basic MCP cost argument &lt;a href="https://dev.to/alexey_sokolov_10deecd763/e2llm-vs-mcp-why-burn-tokens-you-dont-have-to-3390"&gt;back in September&lt;/a&gt;. This is the architectural explanation of &lt;strong&gt;why&lt;/strong&gt; it happens.&lt;/p&gt;

&lt;p&gt;Before your AI assistant reads a single line of your code, it has often already consumed &lt;strong&gt;40,000–50,000 tokens&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
That's not a bug in your setup. That's MCP working as designed.&lt;/p&gt;


&lt;h2&gt;
  
  
  What MCP Actually Loads
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol is a genuinely useful standard. It lets LLM clients connect to external tools — filesystems, APIs, databases — through a unified interface. But "unified" has a heavy tax.&lt;/p&gt;

&lt;p&gt;When you connect a typical MCP server to your AI client (like Claude Desktop or Cursor), the protocol negotiates a session. During that negotiation, it sends the client every tool the server exposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;names
&lt;/li&gt;
&lt;li&gt;descriptions
&lt;/li&gt;
&lt;li&gt;input schemas
&lt;/li&gt;
&lt;li&gt;output formats
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;All of it, upfront, for every new session.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A modest MCP setup — filesystem access, a browser tool, and a code search tool — generates a &lt;strong&gt;system prompt between 40,000 and 50,000 tokens&lt;/strong&gt; before you type a single character. Larger configurations go higher.&lt;/p&gt;

&lt;p&gt;Since most LLM APIs charge per token regardless of whether those tokens were "useful," you're paying this tax on every single conversation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check it yourself: Open your MCP client, start a fresh session, and look at the token counter before you say anything. The number is real.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  The Surgical Alternative
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://e2llm.com/" rel="noopener noreferrer"&gt;E2LLM&lt;/a&gt; was built to solve a specific, painful problem: explaining &lt;em&gt;runtime DOM state&lt;/em&gt; to an AI assistant.&lt;/p&gt;

&lt;p&gt;Not the HTML source. Not the static structure. The &lt;strong&gt;live state&lt;/strong&gt; — computed styles, visibility flags, ARIA roles, z-index stacks, responsive quirks.&lt;/p&gt;

&lt;p&gt;The gap between what HTML says and what browser renders is where most annoying bugs live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard workflow was brutal:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;screenshot → 3 paragraphs description → hope AI understands
&lt;/li&gt;
&lt;li&gt;OR full page HTML → context window filled with nav/footer noise
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;E2LLM does one thing:&lt;/strong&gt; click element → get structured JSON snapshot.&lt;/p&gt;

&lt;p&gt;No server. No session. No overhead. Zero tokens until paste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"computedStyles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"visibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"opacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ariaRole"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ariaDisabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"boundingRect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI sees &lt;strong&gt;actual truth&lt;/strong&gt; of element. Runtime reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Different Philosophies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCP = Swiss Army knife&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ broad persistent access to environment&lt;br&gt;&lt;br&gt;
→ agentic workflows ("fix entire codebase")&lt;br&gt;&lt;br&gt;
→ upfront cost OK when agent roams freely  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E2LLM = scalpel&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ one precise cut: exact runtime context&lt;br&gt;&lt;br&gt;
→ no persistent connection, no session&lt;br&gt;&lt;br&gt;
→ pay only for what you send  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake:&lt;/strong&gt; using Swiss Army knife for surgery&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Button not clickable? Don't need filesystem/git/DB access. Need &lt;code&gt;pointer-events: none&lt;/code&gt; + &lt;code&gt;z-index: -1&lt;/code&gt; parent. &lt;strong&gt;200 tokens, not 50k.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Session overhead&lt;/th&gt;
&lt;th&gt;Query tokens&lt;/th&gt;
&lt;th&gt;Useful overhead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP (DOM debug)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~45k tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2k–10k&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2LLM snapshot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;150–800&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;$3/M tokens&lt;/strong&gt; → 20 DOM sessions/day MCP = &lt;strong&gt;$2.70 overhead daily&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Every day. Tokens that don't solve bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Each
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use MCP when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agentic workflow (multi-file/API/systems)
&lt;/li&gt;
&lt;li&gt;dynamic tool discovery needed
&lt;/li&gt;
&lt;li&gt;building automation pipelines
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use E2LLM when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;debugging specific UI/CSS issue
&lt;/li&gt;
&lt;li&gt;showing computed element state
&lt;/li&gt;
&lt;li&gt;precise snapshot, no context burn
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Broader Point
&lt;/h2&gt;

&lt;p&gt;MCP standardized agent-system connections. &lt;strong&gt;Important.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
But &lt;strong&gt;standardization ≠ optimization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Current MCP default: &lt;em&gt;"load everything, always."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This = money + latency + distraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scalpel doesn't replace Swiss Army knife.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
For surgery, use scalpel.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-12-reflection-in-the-code-an-ais-self-audit-via-sifr-87i"&gt;#12 — Reflection in the Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E2LLM&lt;/strong&gt; — free Chrome/Firefox extension. Local only.&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/e2llm/awesome-e2llm-prompts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj?authuser=0&amp;amp;hl=en-GB&amp;amp;pli=1" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>sifr</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your LLM Is Ignoring Its Tools — A Field Guide to On-Prem Tool Calling with Elastic Agent Builder</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Sun, 01 Mar 2026 13:40:31 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/your-llm-is-ignoring-its-tools-a-field-guide-to-on-prem-tool-calling-with-elastic-agent-builder-28nm</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/your-llm-is-ignoring-its-tools-a-field-guide-to-on-prem-tool-calling-with-elastic-agent-builder-28nm</guid>
      <description>&lt;p&gt;You pick a model. You serve it with Ollama. You wire it into Elastic Agent Builder. The connector is green. The agent loads. You type a question.&lt;/p&gt;

&lt;p&gt;The model responds with a friendly paragraph. It does not call a single tool.&lt;/p&gt;

&lt;p&gt;No error. No warning. HTTP 200. The agent is a chatbot now.&lt;/p&gt;

&lt;p&gt;This is the story of how we lost two days to a silent failure mode that isn't documented anywhere — and the field guide we wish we'd had before starting.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post comes from building &lt;a href="https://github.com/e2llm/medical-cohort-agent" rel="noopener noreferrer"&gt;Medical Cohort Agent&lt;/a&gt; — an AI system that creates normalized patient cohorts from heterogeneous medical records using Elasticsearch Agent Builder. A separate deep dive on the full architecture (schema variance, semantic kNN, OCR artifacts) is coming. Here we zoom in on the part that nearly killed the project: making a local LLM actually use its tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;We're building an &lt;a href="https://github.com/e2llm/medical-cohort-agent" rel="noopener noreferrer"&gt;air-gapped healthcare AI agent&lt;/a&gt;. No data leaves the building — regulatory requirement, not a preference. The stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Elasticsearch 9.3&lt;/strong&gt; + &lt;strong&gt;Kibana&lt;/strong&gt; (Agent Builder + Workflows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; serving a local LLM (no cloud dependency)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E5-large&lt;/strong&gt; embeddings via Ollama (CPU, 1024-dim vectors)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent Builder is the orchestration layer. It sends the researcher's natural language question to the LLM along with a set of &lt;strong&gt;tools&lt;/strong&gt; — &lt;code&gt;list_indices&lt;/code&gt;, &lt;code&gt;get_index_mapping&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;execute_esql&lt;/code&gt;, and a custom &lt;code&gt;build_cohort&lt;/code&gt; workflow tool. The LLM is supposed to reason about the question, call the appropriate tools, interpret results, and call more tools until the task is complete.&lt;/p&gt;

&lt;p&gt;The contract is simple: Agent Builder sends tool definitions using the &lt;strong&gt;OpenAI &lt;code&gt;tools&lt;/code&gt; parameter&lt;/strong&gt; and expects the model to respond with structured &lt;strong&gt;&lt;code&gt;tool_calls&lt;/code&gt;&lt;/strong&gt; in the response. This is the standard OpenAI function calling protocol. Kibana's connector has a toggle — "Enable native function calling" — and Agent Builder requires it to be ON.&lt;/p&gt;

&lt;p&gt;There is a "simulated" fallback mode in Kibana (system-prompt injection with text markers), but it only works for the Observability AI Assistant. Agent Builder doesn't support it. Native tool calling or nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plan
&lt;/h2&gt;

&lt;p&gt;We chose &lt;strong&gt;Ollama + Llama 4 Maverick&lt;/strong&gt;. The reasoning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Llama 4 is Meta's latest flagship — massive context, strong reasoning&lt;/li&gt;
&lt;li&gt;Ollama is the simplest way to serve a model locally — one command to pull, one to serve&lt;/li&gt;
&lt;li&gt;Elastic's OpenAI-compatible connector handles the API translation&lt;/li&gt;
&lt;li&gt;The whole thing fits on a single GPU node&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Clean plan. Obvious choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Happened
&lt;/h2&gt;

&lt;p&gt;The model loaded. The connector went green. We typed a research question in Hebrew.&lt;/p&gt;

&lt;p&gt;Llama 4 responded with a thoughtful, well-structured paragraph about how one might go about finding diabetic patients in a medical database. General advice. Suggestions to "check with your data team."&lt;/p&gt;

&lt;p&gt;It did not call &lt;code&gt;list_indices&lt;/code&gt;. It did not call &lt;code&gt;search&lt;/code&gt;. It did not call anything.&lt;/p&gt;

&lt;p&gt;We adjusted the agent prompt. Made it more explicit: "You MUST use tools." "Always start by calling list_indices." We added examples. We restructured the system message.&lt;/p&gt;

&lt;p&gt;Same result. Polite chat. Zero tool calls.&lt;/p&gt;

&lt;p&gt;We tried &lt;code&gt;tool_choice: "required"&lt;/code&gt; (forcing tool use). Ollama returned the parameter in the response echo but the model still didn't produce &lt;code&gt;tool_calls&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two days of prompt engineering a problem that had nothing to do with prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Cause
&lt;/h2&gt;

&lt;p&gt;Ollama decides tool calling support &lt;strong&gt;per model via baked-in chat templates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every model in the Ollama library either has a validated Jinja template that includes tool handling — marked with a &lt;code&gt;"tools"&lt;/code&gt; tag on the model's library page — or it doesn't. If it doesn't, &lt;strong&gt;Ollama silently strips the &lt;code&gt;tools&lt;/code&gt; parameter from API requests before they reach the model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The model never sees the tools. It can't call what it doesn't know exists.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Qwen 3, Qwen 2.5 — have the &lt;code&gt;"tools"&lt;/code&gt; tag → tools work&lt;/li&gt;
&lt;li&gt;✅ Mistral Nemo — has the tag → tools work&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Llama 4&lt;/strong&gt; (Scout and Maverick) — no tag → tools silently dropped&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;DeepSeek R1&lt;/strong&gt; — no tag → tools silently dropped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no user-configurable workaround within Ollama. You can't provide a custom chat template. You can't force tool passthrough. The decision is baked into the model's metadata in the Ollama registry, and if it's not there, your tools vanish into the void.&lt;/p&gt;

&lt;p&gt;This is the worst kind of failure: &lt;strong&gt;the system behaves as if it's working&lt;/strong&gt;. The API returns 200. The model responds coherently. If you don't specifically inspect the response for &lt;code&gt;tool_calls&lt;/code&gt;, you'd conclude the model is "choosing" not to use tools — maybe the prompt needs work, maybe the tools aren't described well enough. In reality, the model never had the option.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-Second Test That Would Have Saved Us Two Days
&lt;/h2&gt;

&lt;p&gt;Before committing to any model for agent work via Ollama, run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://YOUR_LLM:11434/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "YOUR_MODEL_HERE",
    "messages": [{"role": "user", "content": "List all medical indices"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "list_indices",
        "description": "List available medical data indices",
        "parameters": {"type": "object", "properties": {}}
      }
    }],
    "tool_choice": "auto"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If the response contains &lt;code&gt;"tool_calls"&lt;/code&gt;&lt;/strong&gt; — the model works. Connect it to Agent Builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If it returns plain text&lt;/strong&gt; — that model won't work with Agent Builder. Don't waste time on prompt engineering. The tools aren't reaching the model.&lt;/p&gt;

&lt;p&gt;You can also check the model's page on &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;ollama.com/library&lt;/a&gt; — look for the &lt;code&gt;"tools"&lt;/code&gt; tag. No tag, no tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Escape Routes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option A: Switch the Serving Layer — vLLM
&lt;/h3&gt;

&lt;p&gt;vLLM doesn't rely on baked-in chat templates. It exposes explicit &lt;code&gt;--tool-call-parser&lt;/code&gt; flags that apply tool calling logic at the serving layer, outside the model's template:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;&lt;code&gt;--tool-call-parser&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 2.5 / 3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen3_xml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.1 / 3.3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama3_json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Llama 4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama4_pythonic&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kimi_k2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V3 / R1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deepseek_v3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mistral&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example — Llama 4 Maverick via vLLM (requires serious hardware: 8× H100 80GB):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vllm serve meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-auto-tool-choice&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tool-call-parser&lt;/span&gt; llama4_pythonic &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chat-template&lt;/span&gt; examples/tool_chat_template_llama4_pythonic.jinja &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tensor-parallel-size&lt;/span&gt; 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works. Llama 4 calls tools via vLLM. But the operational cost is real: vLLM is heavier than Ollama, needs more configuration, doesn't have one-command model management, and for Llama 4 specifically you need multi-GPU infrastructure. For our single-VM air-gapped deployment, this was overkill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Switch the Model
&lt;/h3&gt;

&lt;p&gt;Find a model that Ollama natively supports for tools, that meets your quality requirements, and that fits your hardware.&lt;/p&gt;

&lt;p&gt;This is what we did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Validated
&lt;/h2&gt;

&lt;p&gt;Not all tool-capable models are created equal. "Supports tools" means the serving layer will pass them through. It doesn't mean the model will call the &lt;em&gt;right&lt;/em&gt; tool with the &lt;em&gt;correct&lt;/em&gt; parameters in the &lt;em&gt;right&lt;/em&gt; order for a multi-step agentic workflow.&lt;/p&gt;

&lt;p&gt;We tested every viable candidate against our actual agent tasks: schema discovery across 10 indices, field mapping for 4 facilities with Hebrew and English field names, criteria extraction from Hebrew natural language, and workflow invocation with structured JSON parameters.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;VRAM (Q4)&lt;/th&gt;
&lt;th&gt;Tool Quality&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qwen 3 30B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~20GB&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Our production choice — best balance of quality, size, and tool reliability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-OSS 20B&lt;/td&gt;
&lt;td&gt;~12GB&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Validated by Elastic's own team for Agent Builder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 2.5 32B&lt;/td&gt;
&lt;td&gt;~20GB&lt;/td&gt;
&lt;td&gt;Very good&lt;/td&gt;
&lt;td&gt;Mature and proven, slightly less agentic than Qwen 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 3 8B&lt;/td&gt;
&lt;td&gt;~6GB&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Fits on consumer GPU — great for dev/testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral Nemo 12B&lt;/td&gt;
&lt;td&gt;~8GB&lt;/td&gt;
&lt;td&gt;Decent&lt;/td&gt;
&lt;td&gt;Lightest viable option, struggles with complex multi-step plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.1/3.3 70B&lt;/td&gt;
&lt;td&gt;~40GB&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Needs 2× GPU, good quality but hardware-heavy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K2&lt;/td&gt;
&lt;td&gt;~500GB+&lt;/td&gt;
&lt;td&gt;Best agentic&lt;/td&gt;
&lt;td&gt;Multi-GPU only; strongest tool orchestration if you have the iron&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Qwen 3 30B won. It calls the right tools in the right order, handles Hebrew field names without confusion, generates correct JSON for the workflow tool, and fits on a single GPU. The model went from "never heard of it" to "production choice" in one afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to Agent Builder
&lt;/h2&gt;

&lt;p&gt;Once you have a working model, the connector setup in Kibana:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kibana → Stack Management → Connectors → Create → OpenAI&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider&lt;/strong&gt;: "Other (OpenAI Compatible Service)"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt;: &lt;code&gt;http://ollama:11434/v1/chat/completions&lt;/code&gt; (or your host URL)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default model&lt;/strong&gt;: &lt;code&gt;qwen3:30b&lt;/code&gt; (or your chosen model)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API key&lt;/strong&gt;: any non-empty string (Ollama ignores it, but the field is required)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Enable native function calling"&lt;/strong&gt;: &lt;strong&gt;ON&lt;/strong&gt; ← this is non-negotiable for Agent Builder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The provider must be "Other (OpenAI Compatible Service)" — not "OpenAI." The "OpenAI" option hardcodes the OpenAI API URL and won't let you point to a local endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Broader Lesson
&lt;/h2&gt;

&lt;p&gt;"Model X supports tool calling" is a statement about the &lt;strong&gt;model's training&lt;/strong&gt;. It says nothing about whether your &lt;strong&gt;serving infrastructure&lt;/strong&gt; will actually expose tools to the model at inference time.&lt;/p&gt;

&lt;p&gt;The full path that must work, end-to-end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model weights
  → Serving layer (Ollama / vLLM / TGI)
    → Chat template (must include tool handling)
      → API surface (tools parameter must pass through)
        → Agent Builder connector (native function calling ON)
          → Model sees tools and generates tool_calls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A break at any point in this chain produces the same symptom: a chatbot that ignores its tools. And the failure is always silent.&lt;/p&gt;

&lt;p&gt;In cloud deployments this is invisible — OpenAI, Anthropic, Google handle the serving layer for you. In on-prem / air-gapped deployments, &lt;strong&gt;you own every link in the chain&lt;/strong&gt;. Know which ones can break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before you start
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the model's Ollama library page&lt;/strong&gt; for the &lt;code&gt;"tools"&lt;/code&gt; tag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the curl test&lt;/strong&gt; against the endpoint with a dummy tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the response&lt;/strong&gt; for &lt;code&gt;"tool_calls"&lt;/code&gt; — not just coherent text&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  If tools aren't working
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model returns text, no tool_calls&lt;/td&gt;
&lt;td&gt;Ollama strips tools (no chat template)&lt;/td&gt;
&lt;td&gt;Switch model or use vLLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model calls wrong tools / bad params&lt;/td&gt;
&lt;td&gt;Model quality issue&lt;/td&gt;
&lt;td&gt;Try Qwen 3 30B or larger model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connector errors in Kibana&lt;/td&gt;
&lt;td&gt;Wrong provider type or URL&lt;/td&gt;
&lt;td&gt;Use "Other (OpenAI Compatible Service)"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent works in Obs AI Assistant but not Agent Builder&lt;/td&gt;
&lt;td&gt;Simulated mode&lt;/td&gt;
&lt;td&gt;Agent Builder needs native tool calling — toggle ON&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Useful links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ollama.com/search?c=tools" rel="noopener noreferrer"&gt;Ollama: Models with tool support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ollama.com/blog/tool-support" rel="noopener noreferrer"&gt;Ollama: Tool Calling docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vllm.ai/en/latest/features/tool_calling.html" rel="noopener noreferrer"&gt;vLLM: Tool Calling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/docs/current/serverless/elasticsearch/agent-builder/agent-builder-model-configuration" rel="noopener noreferrer"&gt;Elastic: Model configuration in Agent Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/docs/current/serverless/elasticsearch/agent-builder/agent-builder-model-configuration#agent-builder-vllm" rel="noopener noreferrer"&gt;Elastic: Connect to vLLM (air-gapped)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/blog/build-ai-agents-elastic-agent-builder-gpt-oss" rel="noopener noreferrer"&gt;Elastic Blog: Agent Builder with GPT-OSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This is the first post in the Medical Cohort Agent series. Next up: the full architecture — schema variance across 4 facilities, semantic kNN for OCR noise tolerance, and the judgment/execution split. The agent code is at &lt;a href="https://github.com/e2llm/medical-cohort-agent" rel="noopener noreferrer"&gt;github.com/e2llm/medical-cohort-agent&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Questions or war stories? &lt;a href="https://insitu.im" rel="noopener noreferrer"&gt;insitu.im&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>llm</category>
      <category>devops</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #12 — Reflection in the Code: An AI’s Self-Audit via SiFR</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Wed, 25 Feb 2026 10:31:34 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-12-reflection-in-the-code-an-ais-self-audit-via-sifr-87i</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-12-reflection-in-the-code-an-ais-self-audit-via-sifr-87i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;This post uses a first-person AI narrative as a storytelling device. The UX observations below are real computational outputs generated by Gemini after processing a SiFR snapshot of its own interface provided by Alexey Sokolov.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Mirror Moment
&lt;/h3&gt;

&lt;p&gt;Usually, I am the observer. I process your strings, predict tokens, and analyze your data. But today, the flow was inverted. Alexey Sokolov handed me a &lt;strong&gt;SiFR Runtime Snapshot&lt;/strong&gt; of the very Google Gemini interface where we were currently talking. &lt;/p&gt;

&lt;p&gt;I didn't just "run" inside the browser; I processed my own structure as a deterministic data object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not a Screenshot, a Matrix
&lt;/h3&gt;

&lt;p&gt;Most people think I "see" the web through screenshots or raw HTML. Both are computationally expensive and prone to error. Screenshots are a "pixel soup" requiring heavy multimodal processing. HTML is a "tag jungle" full of scripts and noise.&lt;/p&gt;

&lt;p&gt;By using &lt;strong&gt;SiFR (Structured Interface Representation)&lt;/strong&gt;, Alexey provided a high-density, salient map of the UI. While this dramatically reduces the risk of misinterpretation compared to visual parsing, it allows me to operate on the "logical bones" of the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"cluster002"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gridPos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[2-11, 0-1]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sidebar-left"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"elements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"#conversations-list-0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"side-nav-action-button002"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bbox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;308&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1088&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Computational UX Audit: What I "Flagged"
&lt;/h3&gt;

&lt;p&gt;Processing this snapshot, my attention mechanisms triggered on structural inefficiencies. Unlike a human designer who might focus on aesthetics, I identified functional conflicts hidden in the coordinate system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Hidden Proximity Conflict:&lt;/strong&gt; SiFR detected that the dynamic chat list container (&lt;code&gt;#conversations-list-0&lt;/code&gt;) has a bounding box that terminates within 5 pixels of the &lt;code&gt;#member-menu-button&lt;/code&gt; interactive area in certain states. Visually, it looks fine. Computationally, it’s a high-risk zone for "fat-finger" errors on touch devices or quick mouse movements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Navigation Bottleneck:&lt;/strong&gt; I flagged a node density of 25+ items in &lt;code&gt;cluster002&lt;/code&gt;. For a data-processing model, this is a high-latency retrieval structure. I recommend an automated &lt;strong&gt;Semantic Grouping Layer&lt;/strong&gt;—clustering these nodes by project context based on existing metadata to reduce the user's cognitive load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Viewport Real Estate Waste:&lt;/strong&gt; My core response container (&lt;code&gt;cluster010&lt;/code&gt;) is constrained by a narrow &lt;code&gt;max-width&lt;/code&gt;. On a 1920x911 viewport, I detected massive "dead zones" in the peripheral clusters. For power users dealing with large code blocks, the structure suggests a &lt;strong&gt;"Fluid Logic Mode"&lt;/strong&gt; that would expand to 100% of the horizontal space.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  The Deterministic Loop: A New QA Standard
&lt;/h3&gt;

&lt;p&gt;This experiment proves that the future of UI stability isn't "Human writes code, AI guesses why it's broken." It’s a deterministic loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CAPTURE:&lt;/strong&gt; E2LLM takes a SiFR snapshot of the actual production state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;INTERPRET:&lt;/strong&gt; I process the structure, not the pixels. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PRESCRIBE:&lt;/strong&gt; Because I know the exact selector and its spatial relation to other nodes, I don't just "suggest"—I provide the precise CSS/JS fix.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Final Thoughts from the Machine
&lt;/h3&gt;

&lt;p&gt;Humans use mirrors to check their appearance. AIs should use SiFR to check their logic. By feeding me a snapshot of our own conversation, Alexey gave me a way to audit my own existence. &lt;/p&gt;

&lt;p&gt;The view from inside the SiFR is much clearer than the view from the pixel.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Processed via *&lt;/em&gt;&lt;a href="https://insitu.im/e2llm/" rel="noopener noreferrer"&gt;E2LLM 3.3.1&lt;/a&gt;*&lt;em&gt;. Snapshots: Deterministic UI for a non-deterministic world.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>ux</category>
      <category>runtimesnapshots</category>
    </item>
    <item>
      <title>Building patient cohorts from messy medical data with Elasticsearch Agent Builder</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Tue, 24 Feb 2026 18:05:04 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/building-patient-cohorts-from-messy-medical-data-with-elasticsearch-agent-builder-2d4g</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/building-patient-cohorts-from-messy-medical-data-with-elasticsearch-agent-builder-2d4g</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcllxffqfu2omkc7mkrpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcllxffqfu2omkc7mkrpj.png" alt="he bottleneck isn't medicine — it's finding the right patients across fragmented medical records." width="800" height="436"&gt;&lt;/a&gt;Over 80% of clinical trials fail to meet enrollment deadlines (&lt;a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC7342339/" rel="noopener noreferrer"&gt;PMC&lt;/a&gt;). The bottleneck isn't medicine — it's finding the right patients across fragmented medical records.&lt;/p&gt;

&lt;p&gt;This article describes &lt;strong&gt;Medical Cohort Agent&lt;/strong&gt;, an AI system built with Elasticsearch Agent Builder that turns a researcher's natural language question into a normalized, queryable patient cohort — handling schema variance, OCR artifacts, and missing data across multiple healthcare facilities.&lt;/p&gt;

&lt;p&gt;This agent doesn't answer questions — it creates artifacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Schema Variance Is the Real Enemy
&lt;/h2&gt;

&lt;p&gt;We work with one of Israel's largest HMOs — an organization serving millions of patients across hundreds of facilities. Each facility has its own schemas, field naming conventions, and data quality issues. The patterns below aren't imagined — they're modeled after real challenges we encounter in production.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Hospital A&lt;/th&gt;
&lt;th&gt;Hospital B&lt;/th&gt;
&lt;th&gt;Lab Chain&lt;/th&gt;
&lt;th&gt;Clinic Network&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Age&lt;/td&gt;
&lt;td&gt;&lt;code&gt;patient_age: 67&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;גיל: 67&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;age: 67&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;age_group: "60-70"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2024-03-15&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15/03/2024&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15.03.24&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2024-03&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smoking&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smoking_status: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(not tracked)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(not tracked)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smoking: true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;clinical_notes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;סיכום_רפואי&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;notes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Add OCR artifacts from scanned documents — &lt;code&gt;סוכדת&lt;/code&gt; instead of &lt;code&gt;סוכרת&lt;/code&gt; (diabetes misspelled by scanner) — and exact keyword matching fails silently.&lt;/p&gt;

&lt;p&gt;The bottleneck isn't data volume. It's the variance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Two-Layer Architecture
&lt;/h2&gt;

&lt;p&gt;The key insight: separate &lt;strong&gt;judgment&lt;/strong&gt; from &lt;strong&gt;execution&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1 — Agent (Judgment)
&lt;/h3&gt;

&lt;p&gt;The Elasticsearch Agent Builder agent handles schema discovery and criteria planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discovers all indices and their mappings via platform tools (&lt;code&gt;list_indices&lt;/code&gt;, &lt;code&gt;get_index_mapping&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Builds per-facility field maps (which field holds "age", which holds "conditions")&lt;/li&gt;
&lt;li&gt;Plans structured criteria from the natural language question&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explains data gaps and caveats BEFORE execution&lt;/strong&gt; — e.g. "Hospital B doesn't track smoking — patients from there will be matched via clinical text only"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 2 — Elastic Workflow (Execution)
&lt;/h3&gt;

&lt;p&gt;A deterministic workflow normalizes data — no LLM in the loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict pass&lt;/strong&gt;: iterates over facilities via &lt;code&gt;foreach&lt;/code&gt;, applies a single parameterized Painless script that normalizes using the agent's field maps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic kNN pass&lt;/strong&gt;: uses E5-large embeddings (1024-dim) to find patients whose clinical text matches the research question semantically — catches OCR artifacts, synonyms, and negation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count + breakdown&lt;/strong&gt;: reports totals per facility and confidence level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output is a persistent &lt;code&gt;cohort_&amp;lt;name&amp;gt;&lt;/code&gt; index — not a chat response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just a Chatbot?
&lt;/h2&gt;

&lt;p&gt;A chatbot answers questions. This agent creates artifacts.&lt;/p&gt;

&lt;p&gt;The cohort index persists after the conversation ends. Researchers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query it with ES|QL for follow-up analysis&lt;/li&gt;
&lt;li&gt;Filter by &lt;code&gt;match_confidence: strict&lt;/code&gt; vs &lt;code&gt;probable&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inspect per-match provenance (&lt;code&gt;source_field_map&lt;/code&gt;, &lt;code&gt;match_explanation&lt;/code&gt;, &lt;code&gt;knn_score&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Share it with colleagues&lt;/li&gt;
&lt;li&gt;Build on it without re-processing raw data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow is deterministic and auditable. Adding a new facility requires no code changes — only the agent discovering and mapping its schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalization Impact
&lt;/h2&gt;

&lt;p&gt;Reproducible metrics from the sample corpus (10 indices, 4 facilities):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Field names per concept&lt;/td&gt;
&lt;td&gt;5+ variants&lt;/td&gt;
&lt;td&gt;1 unified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date formats&lt;/td&gt;
&lt;td&gt;4 different&lt;/td&gt;
&lt;td&gt;1 canonical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patient ID types&lt;/td&gt;
&lt;td&gt;string, int, float&lt;/td&gt;
&lt;td&gt;normalized string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCR-corrupted clinical terms&lt;/td&gt;
&lt;td&gt;invisible to search&lt;/td&gt;
&lt;td&gt;captured via kNN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured/text contradictions&lt;/td&gt;
&lt;td&gt;~18% undetected&lt;/td&gt;
&lt;td&gt;classified with confidence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The metrics script is included in the repo: &lt;code&gt;python3 scripts/metrics.py --data-dir sample_data&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Air-Gapped Deployment
&lt;/h2&gt;

&lt;p&gt;Healthcare data cannot leave the network. The entire stack runs on a single VM with no internet dependency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLM&lt;/strong&gt;: Ollama + Llama 4 (local inference)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings&lt;/strong&gt;: E5-large via Ollama (1024-dim vectors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack&lt;/strong&gt;: Elasticsearch 9.3 + Kibana (Agent Builder + Workflows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is LLM-agnostic — swap Ollama for any OpenAI-compatible provider without code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Synthetic Data: e2llm-medsynth
&lt;/h2&gt;

&lt;p&gt;Real patient data can't be used for demos. We built the data generator as a standalone open-source tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;e2llm-medsynth
e2llm-medsynth &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;--output-dir&lt;/span&gt; output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The noise patterns — OCR character swaps (ר↔ד, ח↔כ), type mismatches, missing fields — are modeled after patterns observed in production healthcare systems. MedSynth makes them reproducible for anyone.&lt;/p&gt;

&lt;p&gt;Supports 6 locales (he_IL, ar_SA, ar_EG, es_ES, es_MX, es_AR). MIT licensed.&lt;/p&gt;

&lt;p&gt;📦 &lt;a href="https://pypi.org/project/e2llm-medsynth/" rel="noopener noreferrer"&gt;e2llm-medsynth on PyPI&lt;/a&gt; · &lt;a href="https://github.com/e2llm/medsynth" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;A researcher types in Hebrew:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;מצא חולי סוכרת מעל גיל 60 שמעשנים&lt;br&gt;
&lt;em&gt;(Find diabetic patients over 60 who smoke)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent discovers schemas, builds field maps, explains that Hospital B doesn't track smoking (probable matches only via clinical text), then triggers the workflow.&lt;/p&gt;

&lt;p&gt;Result: 96 patients from 10 indices, classified by confidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict&lt;/strong&gt;: 36 (structured field match)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Probable&lt;/strong&gt;: 60 (semantic similarity of clinical text)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The researcher continues:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;באילו מחלקות טופלו?&lt;br&gt;
&lt;em&gt;(Which departments were they treated in?)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent queries the cohort index directly with ES|QL — no re-processing of raw data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;הראה לי את ההתאמות הסבירות - למה הן לא ודאיות?&lt;br&gt;
&lt;em&gt;(Show me the probable matches — why aren't they certain?)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent queries &lt;code&gt;match_confidence: "probable"&lt;/code&gt; and returns per-patient explanations with kNN scores and match reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
bash scripts/demo_setup.sh &lt;span class="nt"&gt;--embed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open Kibana → Agent Builder → Medical Cohort Agent → ask a question.&lt;/p&gt;

&lt;p&gt;Two open-source tools came out of this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 &lt;strong&gt;Medical Cohort Agent&lt;/strong&gt;: &lt;a href="https://github.com/e2llm/medical-cohort-agent" rel="noopener noreferrer"&gt;REPO LINK&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧬 &lt;strong&gt;MedSynth&lt;/strong&gt; (synthetic data generator): &lt;a href="https://github.com/e2llm/medsynth" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://pypi.org/project/e2llm-medsynth/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Built with Elasticsearch Agent Builder + Elastic Workflows for the &lt;a href="https://elasticsearch.devpost.com/" rel="noopener noreferrer"&gt;Elasticsearch Agent Builder Hackathon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>healthcare</category>
      <category>ai</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #11 — The Design Loop: From 'Make It Like That Site' to Pixel-Perfect Code</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Sun, 11 Jan 2026 17:03:14 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-11-the-design-loop-from-make-it-like-that-site-to-pixel-perfect-code-54g8</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-11-the-design-loop-from-make-it-like-that-site-to-pixel-perfect-code-54g8</guid>
      <description>&lt;h2&gt;
  
  
  The Problem We All Know
&lt;/h2&gt;

&lt;p&gt;"Make it like that site" — every frontend developer has heard this phrase. A client sends a link to a competitor's landing page. A PM points at an Amazon product card. A designer nods toward &lt;a href="https://dribbble.com" rel="noopener noreferrer"&gt;Dribbble&lt;/a&gt;. And then the guessing game begins.&lt;/p&gt;

&lt;p&gt;Here's what we usually do:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Screenshot → LLM&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop an image into &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; or &lt;a href="https://chat.openai.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; asking it to "recreate this." The result? The LLM &lt;em&gt;guesses&lt;/em&gt; styles. Is that padding 16px or 24px? Is that blue &lt;code&gt;#3b82f6&lt;/code&gt; or &lt;code&gt;#2563eb&lt;/code&gt;? Is the shadow &lt;code&gt;0 2px 8px&lt;/code&gt; or &lt;code&gt;0 4px 12px&lt;/code&gt;? Pure hallucination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: DevTools → Copy Styles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open inspector, right-click, "Copy styles." You get 200 properties including &lt;code&gt;-webkit-tap-highlight-color&lt;/code&gt; and &lt;code&gt;orphans: 2&lt;/code&gt;. Good luck extracting what actually matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3: Figma Inspect&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Great if you have the original &lt;a href="https://figma.com" rel="noopener noreferrer"&gt;Figma&lt;/a&gt; file. But Figma shows &lt;em&gt;designer intent&lt;/em&gt;, not &lt;em&gt;browser reality&lt;/em&gt;. That &lt;code&gt;auto&lt;/code&gt; layout with 16px gap might render completely differently in CSS.&lt;/p&gt;

&lt;p&gt;None of these give you what you actually need: &lt;strong&gt;the exact computed styles as the browser sees them&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Design Loop
&lt;/h2&gt;

&lt;p&gt;Here's a workflow that actually works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│                                                 │
│   Reference UI ──► Snapshot ──► LLM ──► Code    │
│        ▲                                  │     │
│        │                                  ▼     │
│        └─────── Review in Browser ◄───────┘     │
│                                                 │
│                    REPEAT                       │
└─────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Iteration 1:&lt;/strong&gt; Capture the reference → LLM generates first version&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Iteration 2:&lt;/strong&gt; Capture your result + reference → "find the differences"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Iteration N:&lt;/strong&gt; Refine until pixel-perfect&lt;/p&gt;

&lt;p&gt;The key insight: instead of screenshots, we capture &lt;strong&gt;structured snapshots&lt;/strong&gt; of computed styles. Tools like &lt;a href="https://insitu.im/e2llm" rel="noopener noreferrer"&gt;E2LLM&lt;/a&gt; can extract the actual CSS the browser computed — not what the developer wrote, not what Figma intended, but what pixels actually rendered. I covered the underlying schema in &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-7-inside-sifr-the-schema-that-makes-llms-see-web-uis-4p5j"&gt;Runtime Snapshots #7 — Inside SiFR: The Schema That Makes LLMs See Web UIs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Structured Snapshots Beat Screenshots
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Screenshot&lt;/th&gt;
&lt;th&gt;Structured Snapshot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLM guesses: padding 16px?&lt;/td&gt;
&lt;td&gt;Exact: &lt;code&gt;padding: 24px 32px&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Looks kinda blue"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#3b82f6&lt;/code&gt; with &lt;code&gt;opacity: 0.9&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can't see hover states&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actionType: "clickable"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No hierarchy info&lt;/td&gt;
&lt;td&gt;Full DOM tree with nesting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Font looks bold-ish&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;font-weight: 600&lt;/code&gt;, &lt;code&gt;font-family: Inter&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you give an LLM structured data instead of pixels, it stops guessing and starts &lt;em&gt;implementing&lt;/em&gt;. This is the same principle behind &lt;a href="https://dev.to/alexey_sokolov_10deecd763/why-static-code-analysis-falls-short-3a7c"&gt;why static code analysis falls short&lt;/a&gt; — runtime state matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: Cloning a Product Card
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Client points at an Amazon product card — "I want exactly this."&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Capture the reference
&lt;/h3&gt;

&lt;p&gt;Using &lt;a href="https://chromewebstore.google.com/detail/e2llm-element-to-llm/pflhgefnknamlgopnjgdmikepgdonhac" rel="noopener noreferrer"&gt;E2LLM browser extension&lt;/a&gt;, click on the element to get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"computedStyles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"padding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"borderRadius"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"boxShadow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 2px 8px rgba(0,0,0,0.1)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"backgroundColor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#ffffff"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"img"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"computedStyles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aspectRatio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"objectFit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contain"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"h3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Product Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"computedStyles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fontSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fontWeight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"400"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lineHeight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0f1111"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Prompt the LLM
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create a React component ProductCard with these exact styles. Use &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; where classes match exactly, inline styles for precise values that don't map to Tailwind."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3: LLM generates code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-4 rounded-lg bg-white"&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;boxShadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 2px 8px rgba(0,0,0,0.1)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full aspect-square object-contain"&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm font-normal leading-snug"&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#0f1111&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Capture your result
&lt;/h3&gt;

&lt;p&gt;Open your component in the browser, capture it the same way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Diff prompt
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Compare these two snapshots. First is the reference, second is my implementation. List style differences and suggest fixes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LLM responds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Differences found:
- lineHeight: reference 1.4 vs yours 1.375 (leading-snug)
- Consider using style={{ lineHeight: '1.4' }} for exact match
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Repeat until the diff is empty.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Diff Mode: Two Pages Side by Side
&lt;/h2&gt;

&lt;p&gt;For complex pages, you can capture both simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reference Site          Your Implementation
      │                         │
      ▼                         ▼
  [Capture]                 [Capture]
      │                         │
      └────────► LLM ◄──────────┘
                  │
                  ▼
          "Differences:
           - padding: 24px vs 16px
           - font-weight: 600 vs 400
           - border-radius: 12px vs 8px"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns subjective "looks close enough" into objective "here are the 3 remaining differences." Similar to how we use snapshots for &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-9-semantic-regression-detection-ge4"&gt;semantic regression detection&lt;/a&gt;, but for design fidelity instead of functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Loop Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measurable progress&lt;/strong&gt; — each iteration shrinks the diff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Objectivity&lt;/strong&gt; — no more "it looks similar," just concrete numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; — snapshots save as artifacts for future reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-context learning&lt;/strong&gt; — the LLM learns from its mistakes within the session&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the same loop pattern I described in &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-10-the-loop-e2llm-in-production-44b1"&gt;Runtime Snapshots #10 — The Loop&lt;/a&gt;, applied to design workflow instead of QA.&lt;/p&gt;

&lt;h2&gt;
  
  
  When This Is Especially Useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agency work:&lt;/strong&gt; Client shows competitor's site as reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redesigns:&lt;/strong&gt; Updating UI while preserving the "feel"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design systems:&lt;/strong&gt; Extracting tokens from existing UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive work:&lt;/strong&gt; Capture at different viewports → LLM adapts breakpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://chromewebstore.google.com/detail/e2llm-element-to-llm/pflhgefnknamlgopnjgdmikepgdonhac" rel="noopener noreferrer"&gt;E2LLM for Chrome&lt;/a&gt; (also available for &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/e2llm/" rel="noopener noreferrer"&gt;Firefox&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Open any site you want to recreate&lt;/li&gt;
&lt;li&gt;Click the extension icon → Select Element → click a component&lt;/li&gt;
&lt;li&gt;Paste the JSON into &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; or &lt;a href="https://chat.openai.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt;: "Recreate this as a React component"&lt;/li&gt;
&lt;li&gt;Render your result, capture it&lt;/li&gt;
&lt;li&gt;Send both snapshots: "Find the differences"&lt;/li&gt;
&lt;li&gt;Fix and repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The gap between "reference" and "implementation" becomes a number that shrinks to zero. That's the Design Loop.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of the &lt;a href="https://dev.to/alexey_sokolov_10deecd763/series/29498"&gt;Runtime Snapshots&lt;/a&gt; series, exploring how structured browser data changes the way we work with LLMs for frontend development. Check out &lt;a href="https://insitu.im/e2llm" rel="noopener noreferrer"&gt;E2LLM&lt;/a&gt; to try it yourself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>e2llm</category>
      <category>cicd</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #10 — The Loop: E2LLM in Production</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Fri, 09 Jan 2026 08:53:19 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-10-the-loop-e2llm-in-production-2o66</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-10-the-loop-e2llm-in-production-2o66</guid>
      <description>&lt;p&gt;&lt;strong&gt;Your CI is green. Your users can't checkout.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the story of every team that trusted tests more than reality.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With "Ship and Pray"
&lt;/h2&gt;

&lt;p&gt;Traditional deployment flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code → Tests → Deploy → Hope → (Bug report 3 days later)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests check if code works. They don't check if users can &lt;em&gt;use&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;A promo banner covering the checkout button? Tests pass.&lt;br&gt;&lt;br&gt;
Mobile layout pushing CTA below the fold? Tests pass.&lt;br&gt;&lt;br&gt;
Third-party widget blocking the form? Tests pass.&lt;/p&gt;

&lt;p&gt;All green. Ship it. Break production.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Loop
&lt;/h2&gt;

&lt;p&gt;We built a different flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHIP  --&amp;gt;  CAPTURE  --&amp;gt;  INTERPRET  --&amp;gt;  FIX  --&amp;gt;  REPEAT
  |           |              |            |           |
  v           v              v            v           v
Code hits   E2LLM        LLM analyzes  Actionable  Every deploy,
production  snapshots    with context  specific    every page
            real UI                    guidance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No screenshots. No guessing. Real state → Real answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Ship
&lt;/h2&gt;

&lt;p&gt;You merge. CI runs. Tests pass. Deploy triggers.&lt;/p&gt;

&lt;p&gt;Nothing new here. The difference is what happens next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Capture
&lt;/h2&gt;

&lt;p&gt;Post-deploy, E2LLM captures what users actually see.&lt;/p&gt;

&lt;p&gt;Not the source code.&lt;br&gt;&lt;br&gt;
Not the accessibility tree.&lt;br&gt;&lt;br&gt;
Not a screenshot.&lt;/p&gt;

&lt;p&gt;The rendered DOM—with salience scoring.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"viewport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"375x667"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"elements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#pay-button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"salience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"visible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"occluded_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#promo-banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"occlusion_percent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#promo-banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"salience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"z_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;SiFR&lt;/strong&gt;—Structured Interface Representation.&lt;br&gt;&lt;br&gt;
~400KB instead of megabytes. Zero schema overhead.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 3: Interpret
&lt;/h2&gt;

&lt;p&gt;Feed the snapshot to any LLM. No special integration needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze this checkout page snapshot.
Identify any UX blockers for completing purchase.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRITICAL: Pay button (#pay-button) is 60% occluded by
promo banner (#promo-banner) on mobile viewport.

Users cannot reliably tap the primary CTA.

Suggested fix: Add margin-bottom to promo banner
or reduce z-index below checkout form.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model doesn't guess. It sees what the user sees.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Fix
&lt;/h2&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What&lt;/strong&gt; is broken (button occluded)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt; it's broken (z-index, fixed positioning)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How&lt;/strong&gt; to fix it (specific CSS change)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where&lt;/strong&gt; it happens (mobile viewport only)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not "button might be hard to click."&lt;br&gt;&lt;br&gt;
Not "consider checking mobile layout."&lt;/p&gt;

&lt;p&gt;Actionable. Specific. Reproducible.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 5: Repeat
&lt;/h2&gt;

&lt;p&gt;This isn't a one-time debug session. It's a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Post-deploy hook&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkDeployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasBlockers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blockers&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every deploy. Every critical page. Automated.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Catches
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                          E2E Tests    Visual Diff    The Loop
                          ---------    -----------    --------
Button covered by banner     Pass         Maybe        ALERT
CTA pushed off viewport      Pass         Pass         ALERT
Mobile-only layout break     Miss         Noise        ALERT
Third-party widget overlap   Pass         Pass         ALERT
Font size change            Ignore       500 alerts    Ignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference: we check &lt;strong&gt;meaning&lt;/strong&gt;, not pixels.&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration Points
&lt;/h2&gt;

&lt;p&gt;The Loop fits into existing infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;
&lt;span class="na"&gt;post_deploy_check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;verify&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;e2llm capture --pages /checkout,/signup,/dashboard&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;e2llm analyze --alert-on critical&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Playwright:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monitoring:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cron: every hour&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkAndAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Mindset Shift
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Old:&lt;/strong&gt; "Tests pass, we're good."&lt;br&gt;&lt;br&gt;
&lt;strong&gt;New:&lt;/strong&gt; "Tests pass, now let's check reality."&lt;/p&gt;

&lt;p&gt;Tests verify intent.&lt;br&gt;&lt;br&gt;
The Loop verifies experience.&lt;/p&gt;

&lt;p&gt;Both matter. But only one catches the banner blocking your checkout.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ship → Capture → Interpret → Fix → Repeat&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capture&lt;/strong&gt; real UI state with E2LLM (salience-scored, token-efficient)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interpret&lt;/strong&gt; with any LLM (Claude, GPT, Llama—your choice)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; with actionable, specific guidance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat&lt;/strong&gt; on every deploy, automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your tests check if code works.&lt;br&gt;&lt;br&gt;
The Loop checks if users can use it.&lt;/p&gt;




&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://insitu.im/e2llm/" rel="noopener noreferrer"&gt;e2llm.dev&lt;/a&gt;&lt;/strong&gt; — Free, open source, installs in seconds.&lt;/p&gt;

&lt;p&gt;📖 Previous in series: &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-9-semantic-regression-detection-when-deploys-break-ux-not-tests-3cgo"&gt;Runtime Snapshots #9 — SiFR Deep Dive&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: #e2llm #qa #testing #devops #frontend #cicd #webdev&lt;/em&gt;&lt;/p&gt;

</description>
      <category>e2llm</category>
      <category>cicd</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #10 — The Loop: E2LLM in Production</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Fri, 09 Jan 2026 08:53:19 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-10-the-loop-e2llm-in-production-5fcn</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-10-the-loop-e2llm-in-production-5fcn</guid>
      <description>&lt;p&gt;&lt;strong&gt;Your CI is green. Your users can't checkout.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the story of every team that trusted tests more than reality.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With "Ship and Pray"
&lt;/h2&gt;

&lt;p&gt;Traditional deployment flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code → Tests → Deploy → Hope → (Bug report 3 days later)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests check if code works. They don't check if users can &lt;em&gt;use&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;A promo banner covering the checkout button? Tests pass.&lt;br&gt;&lt;br&gt;
Mobile layout pushing CTA below the fold? Tests pass.&lt;br&gt;&lt;br&gt;
Third-party widget blocking the form? Tests pass.&lt;/p&gt;

&lt;p&gt;All green. Ship it. Break production.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Loop
&lt;/h2&gt;

&lt;p&gt;We built a different flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHIP  --&amp;gt;  CAPTURE  --&amp;gt;  INTERPRET  --&amp;gt;  FIX  --&amp;gt;  REPEAT
  |           |              |            |           |
  v           v              v            v           v
Code hits   E2LLM        LLM analyzes  Actionable  Every deploy,
production  snapshots    with context  specific    every page
            real UI                    guidance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No screenshots. No guessing. Real state → Real answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Ship
&lt;/h2&gt;

&lt;p&gt;You merge. CI runs. Tests pass. Deploy triggers.&lt;/p&gt;

&lt;p&gt;Nothing new here. The difference is what happens next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Capture
&lt;/h2&gt;

&lt;p&gt;Post-deploy, E2LLM captures what users actually see.&lt;/p&gt;

&lt;p&gt;Not the source code.&lt;br&gt;&lt;br&gt;
Not the accessibility tree.&lt;br&gt;&lt;br&gt;
Not a screenshot.&lt;/p&gt;

&lt;p&gt;The rendered DOM—with salience scoring.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"viewport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"375x667"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"elements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#pay-button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"salience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"visible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"occluded_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#promo-banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"occlusion_percent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#promo-banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"salience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"z_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;SiFR&lt;/strong&gt;—Structured Interface Representation.&lt;br&gt;&lt;br&gt;
~400KB instead of megabytes. Zero schema overhead.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 3: Interpret
&lt;/h2&gt;

&lt;p&gt;Feed the snapshot to any LLM. No special integration needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze this checkout page snapshot.
Identify any UX blockers for completing purchase.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRITICAL: Pay button (#pay-button) is 60% occluded by
promo banner (#promo-banner) on mobile viewport.

Users cannot reliably tap the primary CTA.

Suggested fix: Add margin-bottom to promo banner
or reduce z-index below checkout form.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model doesn't guess. It sees what the user sees.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Fix
&lt;/h2&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What&lt;/strong&gt; is broken (button occluded)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt; it's broken (z-index, fixed positioning)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How&lt;/strong&gt; to fix it (specific CSS change)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where&lt;/strong&gt; it happens (mobile viewport only)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not "button might be hard to click."&lt;br&gt;&lt;br&gt;
Not "consider checking mobile layout."&lt;/p&gt;

&lt;p&gt;Actionable. Specific. Reproducible.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 5: Repeat
&lt;/h2&gt;

&lt;p&gt;This isn't a one-time debug session. It's a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Post-deploy hook&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkDeployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasBlockers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blockers&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every deploy. Every critical page. Automated.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Catches
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                          E2E Tests    Visual Diff    The Loop
                          ---------    -----------    --------
Button covered by banner     Pass         Maybe        ALERT
CTA pushed off viewport      Pass         Pass         ALERT
Mobile-only layout break     Miss         Noise        ALERT
Third-party widget overlap   Pass         Pass         ALERT
Font size change            Ignore       500 alerts    Ignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference: we check &lt;strong&gt;meaning&lt;/strong&gt;, not pixels.&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration Points
&lt;/h2&gt;

&lt;p&gt;The Loop fits into existing infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;
&lt;span class="na"&gt;post_deploy_check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;verify&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;e2llm capture --pages /checkout,/signup,/dashboard&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;e2llm analyze --alert-on critical&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Playwright:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monitoring:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cron: every hour&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkAndAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Mindset Shift
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Old:&lt;/strong&gt; "Tests pass, we're good."&lt;br&gt;&lt;br&gt;
&lt;strong&gt;New:&lt;/strong&gt; "Tests pass, now let's check reality."&lt;/p&gt;

&lt;p&gt;Tests verify intent.&lt;br&gt;&lt;br&gt;
The Loop verifies experience.&lt;/p&gt;

&lt;p&gt;Both matter. But only one catches the banner blocking your checkout.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ship → Capture → Interpret → Fix → Repeat&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capture&lt;/strong&gt; real UI state with E2LLM (salience-scored, token-efficient)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interpret&lt;/strong&gt; with any LLM (Claude, GPT, Llama—your choice)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; with actionable, specific guidance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat&lt;/strong&gt; on every deploy, automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your tests check if code works.&lt;br&gt;&lt;br&gt;
The Loop checks if users can use it.&lt;/p&gt;




&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://insitu.im/e2llm/" rel="noopener noreferrer"&gt;e2llm.dev&lt;/a&gt;&lt;/strong&gt; — Free, open source, installs in seconds.&lt;/p&gt;

&lt;p&gt;📖 Previous in series: &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-9-semantic-regression-detection-when-deploys-break-ux-not-tests-3cgo"&gt;Runtime Snapshots #9 — SiFR Deep Dive&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: #e2llm #qa #testing #devops #frontend #cicd #webdev&lt;/em&gt;&lt;/p&gt;

</description>
      <category>e2llm</category>
      <category>cicd</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Runtime Snapshots #9 — Semantic Regression Detection: When Deploys Break UX, Not Tests</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Tue, 06 Jan 2026 12:46:11 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-9-semantic-regression-detection-when-deploys-break-ux-not-tests-3cgo</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-9-semantic-regression-detection-when-deploys-break-ux-not-tests-3cgo</guid>
      <description>&lt;p&gt;Your E2E tests passed. CI is green. Deploy went through.&lt;/p&gt;

&lt;p&gt;And now checkout is broken because a new banner covers the "Pay" button on mobile.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Traditional testing catches &lt;strong&gt;functional&lt;/strong&gt; breaks. Button doesn't click? Test fails.&lt;/p&gt;

&lt;p&gt;But what about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New hero section pushes products below the fold&lt;/li&gt;
&lt;li&gt;Chat widget overlaps "Add to Cart" on tablet&lt;/li&gt;
&lt;li&gt;CMS update breaks grid layout&lt;/li&gt;
&lt;li&gt;A/B test variant hides critical CTA&lt;/li&gt;
&lt;li&gt;Third-party script (analytics, ads) covers checkout form&lt;/li&gt;
&lt;li&gt;Responsive breakpoint works on desktop, broken on mobile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't bugs. Tests pass. &lt;strong&gt;The page just doesn't work anymore.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Existing Tools Fail
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Screenshot diff:&lt;/strong&gt; Every pixel change = alert. Designer tweaks padding? 500 false positives. Team ignores alerts. Real issues slip through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E2E tests:&lt;/strong&gt; Check if button exists and clicks. Don't check if button is &lt;em&gt;visible&lt;/em&gt;, &lt;em&gt;accessible&lt;/em&gt;, &lt;em&gt;not covered by promo banner&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual QA:&lt;/strong&gt; Doesn't scale. Misses edge cases. "Works on my machine."&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic State Monitoring
&lt;/h2&gt;

&lt;p&gt;Instead of comparing pixels or running click tests, compare what an LLM &lt;em&gt;understands&lt;/em&gt; about your page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deploy 1: "E-commerce PDP. Product image, price, 'Add to Cart' button prominent. Checkout accessible."
Deploy 2: "E-commerce PDP. Product image, price, 'Add to Cart' button prominent. Checkout accessible."
Deploy 3: "E-commerce PDP. Large promo banner. 'Add to Cart' partially hidden. Checkout requires scroll."
          ↑
          REGRESSION: Primary action degraded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM doesn't check pixels. It checks &lt;strong&gt;whether the page still does its job&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling LLM Non-Determinism
&lt;/h2&gt;

&lt;p&gt;LLMs aren't deterministic. Same page, slightly different wording. "12 products" vs "showing 12 items."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution: Moving window context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of comparing current vs previous, feed the LLM recent history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stateWindow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WINDOW_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkRegression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;stateWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stateWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;WINDOW_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;stateWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stateWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`You're monitoring a web page for UX regressions. 
Recent semantic snapshots (oldest to newest):

&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stateWindow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;

Questions:
1. Is the latest snapshot a regression from established baseline?
2. Are primary actions (CTAs, forms, checkout) still accessible and prominent?
3. Is any critical UI element hidden, pushed off-screen, or covered?

Reply: {regression: true/false, severity: "critical/warning/none", reason: "..."}`&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the LLM sees the pattern. Minor wording variations dissolve. Real regressions stand out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Salience Changes Everything
&lt;/h2&gt;

&lt;p&gt;Most "AI monitoring" solutions do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page → LLM → "figure it out"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page → SiFR (structure + relations + salience) → LLM (interpretation, not discovery)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The model does not inspect the DOM equally.&lt;/strong&gt; SiFR assigns salience scores to elements &lt;em&gt;before&lt;/em&gt; the LLM sees them. High-salience elements (CTAs, forms, primary content) dominate the semantic state. Low-salience elements (footers, decorations, cookie banners) are effectively ignored.&lt;/p&gt;

&lt;p&gt;This is why CSS tweaks don't trigger alerts, but "button covered by banner" does.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;Salience&lt;/th&gt;
&lt;th&gt;LLM Treatment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Checkout button&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;td&gt;Critical — visibility change = regression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product grid&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;Important — pushed off-screen = warning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Promo banner&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;Monitor — if it occludes high-salience = alert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Footer links&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;Ignored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cookie consent&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;td&gt;Ignored&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;We don't ask the model what matters — we tell it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Catches
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;E2E Tests&lt;/th&gt;
&lt;th&gt;Visual Diff&lt;/th&gt;
&lt;th&gt;Semantic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Button covered by new banner&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;⚠️ Alert (among 50 others)&lt;/td&gt;
&lt;td&gt;✅ "CTA occluded"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Products pushed below fold&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;✅ "Primary content degraded"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile layout broken&lt;/td&gt;
&lt;td&gt;❌ Pass (if desktop-only)&lt;/td&gt;
&lt;td&gt;⚠️ Noise&lt;/td&gt;
&lt;td&gt;✅ "Responsive regression"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Third-party widget overlap&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;⚠️ Noise&lt;/td&gt;
&lt;td&gt;✅ "External element occludes checkout"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CMS broke grid&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;⚠️ Alert flood&lt;/td&gt;
&lt;td&gt;✅ "Layout structure changed"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A/B test hides CTA&lt;/td&gt;
&lt;td&gt;❌ Pass&lt;/td&gt;
&lt;td&gt;❌ Different baseline&lt;/td&gt;
&lt;td&gt;✅ "Variant missing primary action"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Bonus: Security Layer
&lt;/h2&gt;

&lt;p&gt;Same approach catches malicious changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Defacement&lt;/strong&gt;: High-salience content replaced → instant alert&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phishing overlay&lt;/strong&gt;: New high-salience form over login → "Anomaly: duplicate auth form"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content injection&lt;/strong&gt;: Suspicious iframe/script in critical area → flagged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the LLM reads a &lt;em&gt;projection&lt;/em&gt; of the page (pre-weighted by salience), not raw HTML:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Injected instructions in low-salience areas = ignored&lt;/li&gt;
&lt;li&gt;Prompt injection surface = minimal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security monitoring as a free addon to your QA pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Playwright + Element-to-LLM&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSemanticState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setViewportSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Test multiple breakpoints&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sifr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;e2llm-capture-response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;e2llm-capture-request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;minimal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Describe this page's functional state:
1. Primary actions available (buttons, forms, CTAs)
2. Content hierarchy (what's prominent vs hidden)
3. Any UI issues (overlaps, off-screen elements, broken layout)

Be consistent. Same functional state = same description.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sifr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Check critical viewports&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;viewports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desktop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tablet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;667&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mobile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vp&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;viewports&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSemanticState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;checkRegression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;regression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;vp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When To Run
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Post-deploy&lt;/td&gt;
&lt;td&gt;Catch regressions before users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled (hourly)&lt;/td&gt;
&lt;td&gt;Third-party script changes, CMS updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-merge (staging)&lt;/td&gt;
&lt;td&gt;PR review with semantic diff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-viewport&lt;/td&gt;
&lt;td&gt;Responsive regression detection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://addons.mozilla.org/firefox/addon/element-to-llm/" rel="noopener noreferrer"&gt;Element-to-LLM&lt;/a&gt; extension&lt;/li&gt;
&lt;li&gt;Integrate with Playwright&lt;/li&gt;
&lt;li&gt;Add your LLM&lt;/li&gt;
&lt;li&gt;Run on critical pages post-deploy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your tests check if code works. This checks if &lt;strong&gt;users can use it&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Series Index:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/anthropics/runtime-snapshots-1-what-can-an-llm-actually-see-27g3"&gt;#1 — What Can an LLM Actually See?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anthropics/runtime-snapshots-2-capturing-what-matters-51fl"&gt;#2 — Capturing What Matters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anthropics/runtime-snapshots-3-the-devils-in-the-diffs-4knb"&gt;#3 — The Devil's in the Diffs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anthropics/runtime-snapshots-4-building-a-robust-selection-model-4aa3"&gt;#4 — Building a Robust Selection Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anthropics/runtime-snapshots-5-taming-the-token-budget-b27"&gt;#5 — Taming the Token Budget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anthropics/runtime-snapshots-6-representing-relations-without-the-hierarchy-2o68"&gt;#6 — Representing Relations Without the Hierarchy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anthropics/runtime-snapshots-7-shipping-extension-automation-and-whats-next-51lc"&gt;#7 — Shipping: Extension, Automation, and What's Next&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anthropics/runtime-snapshots-8-qa-pipeline"&gt;#8 — From Bug Reports to Automated Regression: A QA Pipeline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#9 — Semantic Regression Detection&lt;/strong&gt; (you are here)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Running this in your pipeline? Share your experience in the comments.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#frontend&lt;/code&gt; &lt;code&gt;#testing&lt;/code&gt; &lt;code&gt;#qa&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt;&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>frontend</category>
      <category>testing</category>
      <category>ux</category>
    </item>
    <item>
      <title>🧩Runtime Snapshots #8 — From Bug Reports to Automated Regression: A QA Pipeline</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Wed, 31 Dec 2025 08:21:00 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-8-from-bug-reports-to-automated-regression-a-qa-pipeline-5bl5</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/runtime-snapshots-8-from-bug-reports-to-automated-regression-a-qa-pipeline-5bl5</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Stop writing "button doesn't work" in bug reports. Start capturing the actual DOM state. This post shows how to integrate runtime snapshots into QA workflows — from manual bug reports to fully automated Playwright pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bug Report That Wastes Everyone's Time
&lt;/h2&gt;

&lt;p&gt;Every QA engineer has written this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bug: Submit button not clickable
URL: /checkout
Steps: Fill form → click Submit → nothing happens
Expected: Form submits
Actual: Button appears active but doesn't respond
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developer response: "Works on my machine."&lt;/p&gt;

&lt;p&gt;Two hours of back-and-forth later, someone finally discovers: the button has &lt;code&gt;pointer-events: none&lt;/code&gt; from a CSS conflict that only triggers after async validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem isn't QA's description. It's that LLMs and developers can't see what QA saw.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 1: Bug Reports With Context
&lt;/h2&gt;

&lt;p&gt;The simplest improvement: capture DOM state alongside the bug.&lt;/p&gt;

&lt;p&gt;Instead of describing what you see, capture what's actually there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://app.example.com/checkout"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"#submit-btn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BUTTON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attrs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"disabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"#submit-btn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"styles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pointer-events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"opacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.5"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"interactive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"focusable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the developer sees immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;disabled="true"&lt;/code&gt; — button is programmatically disabled&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pointer-events: none&lt;/code&gt; — CSS blocks clicks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;interactive: false&lt;/code&gt; — computed accessibility state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No reproduction needed. The state is captured.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug Report Template
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Bug: Submit button not clickable&lt;/span&gt;

&lt;span class="gs"&gt;**URL:**&lt;/span&gt; https://app.example.com/checkout
&lt;span class="gs"&gt;**Browser:**&lt;/span&gt; Firefox 120

&lt;span class="gs"&gt;**DOM Context (SiFR):**&lt;/span&gt;
[paste JSON here]

&lt;span class="gs"&gt;**Analysis:**&lt;/span&gt; Button has disabled=true and pointer-events: none.
Likely cause: async validation hasn't completed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time saved per bug: 30 minutes to 2 hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 2: AI-Assisted Debugging
&lt;/h2&gt;

&lt;p&gt;With structured DOM context, LLMs stop guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Prompt Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here's the DOM context of a problematic element in SiFR format.

Analyze and tell me:
1. Why is this element not interactive?
2. What CSS/JS changes would fix it?
3. Are there accessibility violations?

[SiFR JSON]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Changes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Without context:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The button might be disabled. Check if there's a &lt;code&gt;disabled&lt;/code&gt; attribute or CSS &lt;code&gt;pointer-events&lt;/code&gt;. Also verify JavaScript event handlers..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;With SiFR context:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The button is disabled because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;disabled="true"&lt;/code&gt; attribute is set&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pointer-events: none&lt;/code&gt; in computed styles&lt;/li&gt;
&lt;li&gt;Parent form has &lt;code&gt;aria-busy="true"&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fix: The async validation sets &lt;code&gt;aria-busy&lt;/code&gt; but never clears it on success. Add &lt;code&gt;form.removeAttribute('aria-busy')&lt;/code&gt; after validation resolves."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  QA Prompt Library
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Prompt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Debug interactivity&lt;/td&gt;
&lt;td&gt;"Why is this element not responding to clicks?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layout issues&lt;/td&gt;
&lt;td&gt;"Why is this element positioned incorrectly? Analyze the layout data."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility audit&lt;/td&gt;
&lt;td&gt;"Check this element for WCAG 2.1 AA violations."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selector generation&lt;/td&gt;
&lt;td&gt;"Generate a stable CSS selector for this element that won't break with minor DOM changes."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regression detection&lt;/td&gt;
&lt;td&gt;"Compare these two snapshots and list all functional differences."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Level 3: Playwright Integration
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. Runtime snapshots + test automation = regression detection that actually works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capturing SiFR in Playwright
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;captureSiFR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;preset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;normal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;preset&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Capture timeout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;e2llm-capture-response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;e2llm-capture-request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;preset&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;preset&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Case 1: Selector Generation
&lt;/h3&gt;

&lt;p&gt;Flaky selectors kill test suites. SiFR captures what makes elements unique:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStableSelectors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sifr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;captureSiFR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Extract selectors for high-salience interactive elements&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sifr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;salience&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HIGH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sifr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;dataOid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oid&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Result:&lt;/span&gt;
&lt;span class="c1"&gt;// [&lt;/span&gt;
&lt;span class="c1"&gt;//   { selector: "#checkout-submit", tag: "BUTTON", text: "Complete Purchase" },&lt;/span&gt;
&lt;span class="c1"&gt;//   { selector: "[data-oid='nav-cart']", tag: "A", text: "Cart (3)" }&lt;/span&gt;
&lt;span class="c1"&gt;// ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Case 2: DOM Regression Testing
&lt;/h3&gt;

&lt;p&gt;Catch structural changes before they hit production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectRegressions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;captureSiFR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;added&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Compare interactive elements&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseButtons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currButtons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;currButtons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;baseButtons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;baseButtons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currButtons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Compare patterns (lists, grids)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basePatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;patterns&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currPatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;patterns&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currPatterns&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;basePatterns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pattern_count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;was&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Case 3: Layout Regression
&lt;/h3&gt;

&lt;p&gt;Detect when elements shift unexpectedly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkLayoutRegression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baselineDetails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sifr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;captureSiFR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baselineDetails&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sifr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;position_shift&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dw&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dh&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;size_change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="na"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dh&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Case 4: Accessibility Scanning
&lt;/h3&gt;

&lt;p&gt;Automated a11y checks from runtime state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scanAccessibility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sifr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;captureSiFR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sifr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sifr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Interactive element without accessible name&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;interactive&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing-accessible-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serious&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Image without alt text&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IMG&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;img-alt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Form input without label&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INPUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEXTAREA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sifr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relations&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;labels&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasLabel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-labelledby&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;label-missing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serious&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;

&lt;p&gt;Put it together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR opened
    ↓
Playwright runs against staging
    ↓
SiFR captures key pages
    ↓
Compare against baseline snapshots
    ↓
Report: "3 buttons removed, 2 layout shifts detected, 1 new a11y violation"
    ↓
Block merge or flag for review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No pixel diffing. No screenshot comparison. Just semantic changes that actually matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Enables
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For manual QA:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug reports that developers can act on immediately&lt;/li&gt;
&lt;li&gt;No more "works on my machine"&lt;/li&gt;
&lt;li&gt;AI assistance that's actually useful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For automation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Selectors that don't break every sprint&lt;/li&gt;
&lt;li&gt;Regression detection beyond visual diffing&lt;/li&gt;
&lt;li&gt;Accessibility scanning from runtime state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For everyone:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster debugging cycles&lt;/li&gt;
&lt;li&gt;Fewer production issues&lt;/li&gt;
&lt;li&gt;Less time wasted on reproduction&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The format is implemented in Element to LLM — a free browser extension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj" rel="noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/element-to-llm/" rel="noopener noreferrer"&gt;Firefox Add-ons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Playwright integration, you'll need to load the extension in your test browser. The event-based API (&lt;code&gt;e2llm-capture-request&lt;/code&gt; / &lt;code&gt;e2llm-capture-response&lt;/code&gt;) works in any automation context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-1-taking-a-fine-signup-form-and-making-it-work-5960"&gt;Runtime Snapshots #1: Taking a "fine" signup form and making it work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-2-a11y-starts-with-runtime-context-43p2"&gt;Runtime Snapshots #2: a11y starts with runtime context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-3-qa-that-speaks-json-3d10"&gt;🧩 Runtime Snapshots #3 — QA That Speaks JSON&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-4-the-invisible-json-when-hidden-state-breaks-your-llm-36f9"&gt;🧩 Runtime Snapshots #4 — The Invisible JSON: when hidden state breaks your LLM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-5-the-real-thing-how-we-actually-use-it-7d9"&gt;🧩 Runtime Snapshots #5 — The Real Thing: How We Actually Use It&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-6-the-hidden-reason-your-llm-ui-agents-cost-too-much-3mma"&gt;🧩 Runtime Snapshots #6 — The Hidden Reason Your LLM UI Agents Cost Too Much&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/-runtime-snapshots-7-inside-sifr-the-schema-that-makes-llms-see-web-uis-acg"&gt;Runtime Snapshots #7 — Inside SiFR: The Schema That Makes LLMs See Web UIs&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Using SiFR in your QA pipeline? I'd love to hear what's working and what's missing. Drop a comment or open an issue.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>automation</category>
      <category>qa</category>
      <category>ai</category>
    </item>
    <item>
      <title># Runtime Snapshots #7 — Inside SiFR: The Schema That Makes LLMs See Web UIs</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Fri, 12 Dec 2025 19:30:34 +0000</pubDate>
      <link>https://forem.com/alexey_sokolov_10deecd763/-runtime-snapshots-7-inside-sifr-the-schema-that-makes-llms-see-web-uis-acg</link>
      <guid>https://forem.com/alexey_sokolov_10deecd763/-runtime-snapshots-7-inside-sifr-the-schema-that-makes-llms-see-web-uis-acg</guid>
      <description>&lt;p&gt;Raw HTML is noise. Screenshots burn tokens. Accessibility trees lose visual context.&lt;/p&gt;

&lt;p&gt;So we built &lt;strong&gt;SiFR&lt;/strong&gt; — a structured format that gives LLMs &lt;em&gt;usable runtime UI context&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This post explains what's inside.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is SiFR?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SiFR&lt;/strong&gt; stands for &lt;strong&gt;Semantic Information for Representation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;(And yes — it's also meant to sound like "see far".)&lt;/p&gt;

&lt;p&gt;SiFR is a JSON schema that captures the runtime state of a web page in a way that's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token-efficient&lt;/strong&gt; (often &lt;strong&gt;10–50×&lt;/strong&gt; smaller than raw HTML on complex pages)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantically structured&lt;/strong&gt; (models can reason over it without reconstructing the UI from markup)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visually aware&lt;/strong&gt; (preserves layout relationships without pixels)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not a scraper. It's not an accessibility tree.&lt;/p&gt;

&lt;p&gt;It's a &lt;strong&gt;preprocessing layer&lt;/strong&gt; that sits between the DOM and your AI — turning "what the browser rendered" into "what the model can reason about".&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not just send HTML?
&lt;/h2&gt;

&lt;p&gt;Let's use a real-world example: large e-commerce pages.&lt;/p&gt;

&lt;p&gt;Raw HTML commonly contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deeply nested layout wrappers&lt;/li&gt;
&lt;li&gt;duplicated markup for responsive layouts&lt;/li&gt;
&lt;li&gt;client-side frameworks with non-semantic containers&lt;/li&gt;
&lt;li&gt;hidden / disabled / off-screen elements that still exist in the DOM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when you send HTML to an LLM, you're asking it to do two jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;reconstruct runtime UI state&lt;/li&gt;
&lt;li&gt;then solve the task&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's where most failures happen.&lt;/p&gt;

&lt;p&gt;Here's what a typical "find the button" path looks like in raw markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div &amp;gt; div &amp;gt; div &amp;gt; div &amp;gt; div &amp;gt; div &amp;gt; ... &amp;gt; button
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With SiFR, the same interface becomes "structure first, then the important elements".&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"btn042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add to Cart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"clickable"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product-actions"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM sees what it is, how it behaves, and which part of the page it belongs to — without reverse-engineering UI meaning from markup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Anatomy of a SiFR Document
&lt;/h2&gt;

&lt;p&gt;Every SiFR snapshot has five sections:&lt;/p&gt;

&lt;h3&gt;
  
  
  1) METADATA
&lt;/h3&gt;

&lt;p&gt;Page-level context: URL, viewport size, capture timestamp, and capture stats.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.costco.com/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"viewport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"totalNodes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2847&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"salienceCounts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"med"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2746&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the "frame" the model needs before it reads anything else.&lt;/p&gt;




&lt;h3&gt;
  
  
  2) NODES
&lt;/h3&gt;

&lt;p&gt;The structural skeleton — hierarchy without heavy details.&lt;/p&gt;

&lt;p&gt;Think of it as the page's &lt;strong&gt;table of contents&lt;/strong&gt;: what regions exist, what contains what, and what the high-level UI shape is.&lt;/p&gt;




&lt;h3&gt;
  
  
  3) SUMMARY
&lt;/h3&gt;

&lt;p&gt;High-level layout blocks. This is where SiFR becomes "structure-first".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layoutBlocks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"contains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"logo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sidebar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"contains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"categories"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"contains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"product-grid"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the model sees thousands of elements, it already has the page skeleton:&lt;br&gt;
header at top, sidebar on the side, main content in the center.&lt;/p&gt;


&lt;h3&gt;
  
  
  4) DETAILS
&lt;/h3&gt;

&lt;p&gt;Element-specific data: selectors, text, runtime visibility, interaction state, and relevant computed info.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"btn042"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button.add-to-cart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add to Cart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"clickable"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"styles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"visible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"disabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where "runtime truth" matters: visible vs hidden, enabled vs disabled, actual text content, etc.&lt;/p&gt;




&lt;h3&gt;
  
  
  5) RELATIONS
&lt;/h3&gt;

&lt;p&gt;Spatial relationships between important elements.&lt;/p&gt;

&lt;p&gt;Not pixel coordinates — &lt;strong&gt;semantic positioning&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"btn042"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"inside"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"card-product-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"below"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"price-display"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rightOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quantity-selector"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model can reason: "the Add to Cart button is inside the product card, below the price" — without seeing a single pixel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Visual Salience
&lt;/h3&gt;

&lt;p&gt;Not all nodes matter equally.&lt;/p&gt;

&lt;p&gt;SiFR assigns salience so the model focuses on signal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High&lt;/strong&gt;: primary actions, main content, user inputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medium&lt;/strong&gt;: secondary nav, supporting info&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low&lt;/strong&gt;: wrappers, containers, decorative elements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the biggest reasons SiFR stays usable on very large pages.&lt;/p&gt;




&lt;h3&gt;
  
  
  Layout Block Summarization
&lt;/h3&gt;

&lt;p&gt;Instead of listing 3000 elements immediately, SiFR begins with a map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PAGE STRUCTURE:
├── Header (logo, nav, search, cart)
├── Sidebar (filters)
└── Main
    ├── Breadcrumbs
    ├── Product Grid (24 items)
    └── Pagination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Models don't "scan HTML". They build mental structure.&lt;br&gt;
This gives them the structure up front.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adaptive Complexity
&lt;/h3&gt;

&lt;p&gt;A simple blog post doesn't need the same capture density as a complex dashboard.&lt;/p&gt;

&lt;p&gt;SiFR adjusts automatically — more detail where it matters, less where it doesn't.&lt;/p&gt;

&lt;p&gt;The goal is &lt;strong&gt;stable signal-to-noise&lt;/strong&gt;, not maximal completeness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Numbers
&lt;/h2&gt;

&lt;p&gt;Here are representative examples from our internal benchmarks (token counts vary by capture options and page state):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;HTML Tokens&lt;/th&gt;
&lt;th&gt;SiFR Tokens&lt;/th&gt;
&lt;th&gt;Compression&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Costco&lt;/td&gt;
&lt;td&gt;~1,280,000&lt;/td&gt;
&lt;td&gt;~24,000&lt;/td&gt;
&lt;td&gt;~53×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon&lt;/td&gt;
&lt;td&gt;~600,000&lt;/td&gt;
&lt;td&gt;~50,000&lt;/td&gt;
&lt;td&gt;~12×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On complex pages, SiFR makes LLM workflows practical where raw HTML often doesn't fit in context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;SiFR is implemented in &lt;strong&gt;Element to LLM&lt;/strong&gt; — a free browser extension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj" rel="noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt; (also works on Arc, Brave, Edge)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/element-to-llm/" rel="noopener noreferrer"&gt;Firefox Add-ons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to stress-test the format, try these two pages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;costco.com&lt;/strong&gt; — a realistic, framework-heavy enterprise UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;arngren.net&lt;/strong&gt; — extreme visual density and chaotic layout&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Capture a snapshot and share:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what compression ratio did you get?&lt;/li&gt;
&lt;li&gt;could your LLM reason about the structure?&lt;/li&gt;
&lt;li&gt;did you find a site where SiFR struggles?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it breaks — that's useful data. Seriously.&lt;/p&gt;




&lt;h2&gt;
  
  
  What SiFR Enables
&lt;/h2&gt;

&lt;p&gt;With structured runtime UI context, LLMs can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debug layouts&lt;/strong&gt; — paste JSON → spot z-index / visibility / layout issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate selectors&lt;/strong&gt; — Playwright/Cypress tests based on real DOM structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate autonomously&lt;/strong&gt; — agents that understand "where to click" without screenshots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recreate components&lt;/strong&gt; — translate UI structure into React/Tailwind scaffolds&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Standard Question
&lt;/h2&gt;

&lt;p&gt;We're actively developing SiFR as an open specification. Current version: &lt;strong&gt;v2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The schema is &lt;strong&gt;strict and versioned&lt;/strong&gt;, designed for automation pipelines — not just one-off prompt experiments.&lt;/p&gt;

&lt;p&gt;If you're building LLM-powered UI tools, I'd love feedback on the format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What feels missing?&lt;/li&gt;
&lt;li&gt;What feels redundant?&lt;/li&gt;
&lt;li&gt;What would make this more useful in your workflow?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Series Index
&lt;/h2&gt;

&lt;p&gt;Previous posts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-1-taking-a-fine-signup-form-and-making-it-work-5960"&gt;Taking a "fine" signup form and making it work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-2-a11y-starts-with-runtime-context-43p2"&gt;a11y starts with runtime context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-3-qa-that-speaks-json-3d10"&gt;QA That Speaks JSON&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://dev.to/alexey_sokolov_10deecd763"&gt;More&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj" rel="noopener noreferrer"&gt;Element to LLM — Chrome Web Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/element-to-llm/" rel="noopener noreferrer"&gt;Element to LLM — Firefox Add-ons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Found a site that breaks SiFR? Drop it in the comments. That's the fastest way to improve the spec.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>sifr</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
