# 5 Things I Learned Building a Chrome Extension That Watches ChatGPT, Claude & Gemini

> Source: <https://dev.to/juliedechili/5-things-i-learned-building-a-chrome-extension-that-watches-chatgpt-claude-gemini-4a4i>
> Published: 2026-07-16 09:43:05+00:00

I spent the last few months building a Chrome extension that detects HTML code blocks inside ChatGPT, Claude, and Gemini and lets you deploy them straight to a live URL. The "deploy" part turned out to be the easy 20%. The hard 80% was reliably watching three completely different, constantly-changing chat UIs without breaking every other week.

Here's what actually taught me something.

`MutationObserver`

is non-negotiable, but it will still lie to you
None of these chat apps render the full response at once — they stream tokens in, which means the DOM you're watching is *incomplete* almost every time your observer fires.

My first version tried to detect a finished `<pre><code>`

block the moment it appeared. Result: I was grabbing HTML mid-stream, cut off halfway through a `<div>`

.

What actually worked was debouncing on DOM stability instead of DOM presence:

``` js
let debounceTimer;
const observer = new MutationObserver(() => {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(scanForCodeBlocks, 600);
});
observer.observe(document.body, { childList: true, subtree: true });
```

600ms of "nothing changed" turned out to be a much more reliable signal than "the tag now exists." Not elegant, but it works across all three sites' streaming speeds.

ChatGPT, Claude, and Gemini all ship frequent frontend updates, and none of them are obligated to keep a stable class name for you to hook into. I initially selected code blocks by class name (`.language-html`

, `.hljs`

, etc.) and had selectors silently break in production within two weeks of launch.

What's held up better: matching on **structural patterns** instead of class names — a `<pre>`

containing a `<code>`

whose text content starts with `<!DOCTYPE`

or `<html`

. It's slower to write the first time, but it doesn't care what CSS class the framework decided to use this month.

A single AI response can contain multiple code blocks — a full HTML page, a CSS snippet the model explains separately, a JS fix suggested afterward. Grabbing "the first `<pre>`

tag" is wrong more often than you'd think.

I ended up scoring candidate blocks instead of just taking the first match: presence of `<!DOCTYPE>`

or `<html>`

scores highest, presence of `<head>`

/`<body>`

scores next, and blocks under ~50 characters get discarded outright (usually just an inline example, not a real page). It's not perfect, but it cut false positives dramatically.

`srcdoc`

My first preview implementation just dumped the extracted HTML into an `iframe`

with `srcdoc`

. It worked until someone's AI-generated page included a `<script>`

that tried to reach `window.parent`

— completely benign in their case, but it's exactly the kind of thing you don't want silently allowed in an extension with content-script access to the current page.

```
<iframe
  sandbox="allow-scripts allow-same-origin"
  srcdoc="...">
</iframe>
```

Locking `sandbox`

down explicitly, and being deliberate about *which* permissions you grant back, is one of those five-minute fixes that matters a lot more than it looks like it does.

I expected the first questions after launch to be about which hosts were supported. Instead, almost everyone's first question was some version of: *"where does my FTP password go?"*

That pushed me toward a design I'd recommend to anyone building a browser extension that touches any kind of credential:

None of this is exotic security engineering. But "where does my password go" is apparently the single highest-trust-impacting sentence you can answer clearly in your docs.

The extension this came out of is called **HTML Deployer** — it takes the HTML that ChatGPT/Claude/Gemini generate and pushes it to Netlify, GitHub Pages, FTP, or a self-hosted target, with a preview step in between. If you've hit similar DOM-stability or sandboxing problems building on top of these chat UIs, I'd genuinely like to compare notes in the comments.
