{"slug": "5-things-i-learned-building-a-chrome-extension-that-watches-chatgpt-claude", "title": "5 Things I Learned Building a Chrome Extension That Watches ChatGPT, Claude & Gemini", "summary": "A developer built a Chrome extension called HTML Deployer that detects HTML code blocks inside ChatGPT, Claude, and Gemini and deploys them to live URLs. Key lessons include using MutationObserver with debouncing for DOM stability, matching structural patterns instead of class names to survive frequent UI updates, scoring candidate code blocks to avoid false positives, explicitly locking down iframe sandbox permissions, and clearly communicating credential handling to build user trust.", "body_md": "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.\n\nHere's what actually taught me something.\n\n`MutationObserver`\n\nis non-negotiable, but it will still lie to you\nNone 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.\n\nMy first version tried to detect a finished `<pre><code>`\n\nblock the moment it appeared. Result: I was grabbing HTML mid-stream, cut off halfway through a `<div>`\n\n.\n\nWhat actually worked was debouncing on DOM stability instead of DOM presence:\n\n``` js\nlet debounceTimer;\nconst observer = new MutationObserver(() => {\n  clearTimeout(debounceTimer);\n  debounceTimer = setTimeout(scanForCodeBlocks, 600);\n});\nobserver.observe(document.body, { childList: true, subtree: true });\n```\n\n600ms 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.\n\nChatGPT, 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`\n\n, `.hljs`\n\n, etc.) and had selectors silently break in production within two weeks of launch.\n\nWhat's held up better: matching on **structural patterns** instead of class names — a `<pre>`\n\ncontaining a `<code>`\n\nwhose text content starts with `<!DOCTYPE`\n\nor `<html`\n\n. It's slower to write the first time, but it doesn't care what CSS class the framework decided to use this month.\n\nA 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>`\n\ntag\" is wrong more often than you'd think.\n\nI ended up scoring candidate blocks instead of just taking the first match: presence of `<!DOCTYPE>`\n\nor `<html>`\n\nscores highest, presence of `<head>`\n\n/`<body>`\n\nscores 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.\n\n`srcdoc`\n\nMy first preview implementation just dumped the extracted HTML into an `iframe`\n\nwith `srcdoc`\n\n. It worked until someone's AI-generated page included a `<script>`\n\nthat tried to reach `window.parent`\n\n— 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.\n\n```\n<iframe\n  sandbox=\"allow-scripts allow-same-origin\"\n  srcdoc=\"...\">\n</iframe>\n```\n\nLocking `sandbox`\n\ndown 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.\n\nI 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?\"*\n\nThat pushed me toward a design I'd recommend to anyone building a browser extension that touches any kind of credential:\n\nNone 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.\n\nThe 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.", "url": "https://wpnews.pro/news/5-things-i-learned-building-a-chrome-extension-that-watches-chatgpt-claude", "canonical_source": "https://dev.to/juliedechili/5-things-i-learned-building-a-chrome-extension-that-watches-chatgpt-claude-gemini-4a4i", "published_at": "2026-07-16 09:43:05+00:00", "updated_at": "2026-07-16 10:04:35.337717+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "large-language-models", "ai-products"], "entities": ["ChatGPT", "Claude", "Gemini", "HTML Deployer", "Netlify", "GitHub Pages", "FTP"], "alternates": {"html": "https://wpnews.pro/news/5-things-i-learned-building-a-chrome-extension-that-watches-chatgpt-claude", "markdown": "https://wpnews.pro/news/5-things-i-learned-building-a-chrome-extension-that-watches-chatgpt-claude.md", "text": "https://wpnews.pro/news/5-things-i-learned-building-a-chrome-extension-that-watches-chatgpt-claude.txt", "jsonld": "https://wpnews.pro/news/5-things-i-learned-building-a-chrome-extension-that-watches-chatgpt-claude.jsonld"}}