The Fragile DOM War: Why We Extracted decant-core

24 Aug 2026

Taming nineteen shifting AI frontends and building a shared open extraction layer so the ecosystem only has to fix the scraper once.

If you want to understand the modern web at its most cynical, try maintaining a browser scraper across multiple AI chat platforms for more than four weeks.

When I released the original chat exporter and then followed it up by killing the cloud proxy for Decant, I thought the hard part was behind me. The architecture was clean: local execution, zero tracking, direct file downloads.

Then reality hit.

AI providers do not publish stable public APIs for reading your own rendered conversation history. If you want to pull a conversation out of ChatGPT, Claude, Gemini, Perplexity, or DeepSeek, you have to walk the Document Object Model. You have to inspect the React fiber tree, trace internal hydration attributes, or traverse arbitrary nested div elements that change whenever a frontend designer in San Francisco gets bored on a Tuesday afternoon.

One week OpenAI replaces a semantic article tag with an obscure flexbox container. The next week Anthropic ships a minor UI refresh that wraps every code block in an extra shadow root. Suddenly, half the bug reports in your repository are people screaming that their export button is spitting out blank Markdown files.

The insult is not that the markup changes. The insult is the collective waste.

Across GitHub, there are dozens of independent developers maintaining their own isolated browser extensions, Obsidian sync plugins, and command line archivers. Every single one of them is solving the exact same annoying puzzle in isolation. When Anthropic changes their message container class, fifty different programmers sit down in fifty different bedrooms and spend four hours reverse-engineering the exact same CSS selector.

It is an absurd duplication of human misery.

By late August, I was maintaining two separate extensions, each with its own internal DOM extraction logic. Keeping them in sync was driving me up the wall. So I stripped the entire extraction layer out of both codebases and forged it into a shared, standalone npm package: decant-core.

The mandate was simple: presentation belongs in the extension, but extraction belongs in a shared foundation.

decant-core is an AGPL-3.0 library built to handle the entire dirty pipeline of turning unstructured, hostile web pages into clean data:

  1. Nineteen AI Chat Platform Parsers: It doesn't just scrape text; it extracts structured conversational models. You get clean message objects, roles, model identifiers, timestamps, and normalized formatting across ChatGPT, Claude, Gemini, DeepSeek, Mistral, Perplexity, and thirteen others.
  2. Triple Path Web Extraction: For regular web articles, relying on Mozilla Readability alone leaves too many broken pages. decant-core runs Mozilla Readability, Defuddle, and Article-Extractor in parallel, scoring the output through a quality arbitration heuristic to pick the cleanest candidate.
  3. Detection Utilities: It can look at an arbitrary active tab and immediately determine whether it is an AI chat session or a regular publication before deciding which parsing strategy to apply.
  4. Math and Markdown Sanitation: It fixes broken LaTeX delimiters, eliminates duplicate MathML garbage, and fences nested code blocks so the final file actually renders properly in Obsidian, Logseq, and Notion without manual cleanup.

The package is fully tree-shakeable and works natively in modern browser runtimes and Manifest V3 extension service workers.

The strategic point of this is maintenance. When a platform breaks its DOM, we don't need five dozen different developers scrambling to rewrite their scrapers. The fix happens once, in decant-core, and any extension or research tool built on top of it gets the repair on their next build.

You can inspect the code on GitHub or install it directly via npm (npm install decant-core).

Free software only wins against proprietary platform churn when we stop writing the same fifty brittle utilities in private silos. If we are going to fight the platform lock-in war, we might as well share the weapons.


Image Credit: Joseph Wright of Derby (1734-1797) - An Iron Forge via Google Art Project / Wikimedia Commons.