Micro-frontend shell never bootstraps: style-loader "Couldn't find a style target" on developer.intuit.com (headless Chrome unaffected)
Before you start
- I searched existing issues and this is not a duplicate.
- I tested against the latest release or
main. - This is not a security vulnerability (those go through SECURITY.md, not a public issue).
obscura version or commit
main @ 14da23d (2026-08-31), built from source via the repo Dockerfile. Binary self-reports obscura 0.1.0. Also reproduces identically on the published v0.2.1 image (h4ckf0r0day/obscura:latest, digest sha256:e65cb455fc67543283da6901e8735c45aab5421e2ced8879b0a1fa70a4e38a2d).
OS and architecture
linux/amd64 container (Docker 27.1.1, Docker Desktop on Windows 11 x64 host).
How are you using obscura?
CLI (fetch / serve / scrape / mcp)
Affected area
JavaScript or DOM behavior
Build variant
Render-enabled (official default archive or --features render)
Stealth mode
Off
Reproduction
docker run --rm h4ckf0r0day/obscura:latest fetch \
"https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/customer" \
--dump textPublic URL, no auth required. Also tried, with byte-identical output:
# valid settle strategies, generous budget
--wait-until networkidle0 --wait 25 --timeout 90
--wait-until networkidle2 --wait 25 --timeout 90
--dump markdown
--stealthAlso tried --stealth; no change. The site's own telemetry reports "isScraperBot":false, "browserType":"green", so this is not bot detection.
Expected behavior
The rendered API reference documentation, ~53,600 characters, page title API Reference, containing the string SyncToken. That is what headless Chrome produces on the same URL.
Actual behavior
46 characters — the shell's loading placeholder only:
Compiling and pre-filling your Intuit info...Title stays Intuit Developer (the shell's own title, never replaced by the app's). The page is a micro-frontend shell that loads its content plugin (ext-dev-portal-ui) at runtime; that load fails and the app never mounts.
DOM state after settle, same URL, same moment, obscura vs headless Chrome:
| Signal | headless Chrome | obscura main |
|---|---|---|
document.querySelectorAll('script').length |
107 | 49 |
document.head.children.length |
196 | 112 |
document.querySelectorAll('style').length |
61 | 19 |
DIV#___appshell (the mounted app) |
present | absent |
DIV#web-shell-spinner (the loader) |
present | present |
| extractable body text | 53,623 chars | 46 chars |
Script loading stops roughly halfway and the app root element is never created.
Chromium (Playwright 1.62, same container network, same moment) renders 53,623 chars with #___appshell present, 103 console messages and 0 page errors, and produces zero occurrences of style target, AppPluginBundle, AppPluginLoader or Bootstrap failed. Every error below is obscura-only.
Rendering details
No response
Does headless Chrome behave the same?
No: headless Chrome renders or behaves correctly and obscura does not
Logs
Page loaded: https://developer.intuit.com/... - "Intuit Developer"
ERROR obscura::console: Shell - failed to fetch AppPluginBundle {...,"appPlugin":"ext-dev-portal-ui","appPluginVersion":"1.69.5","clientMode":"WEB","runtime":"CSR",...}
Error: Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.
at u (eval at execute (<obscura:bootstrap>:369:28), <anonymous>:1:3585)
at v (eval at execute (<obscura:bootstrap>:369:28), <anonymous>:1:4622)
at c (eval at execute (<obscura:bootstrap>:369:28), <anonymous>:1:3267)
at e.exports (eval at execute (<obscura:bootstrap>:369:28), <anonymous>:1:4955)
ERROR obscura::console: Shell - failed to fetch appPlugin from AppPluginLoader {...}
Error: Couldn't find a style target. ...
ERROR obscura::console: Shell - Application Bootstrap failed {...}
Error: Couldn't find a style target. ...
The same error text propagates through all three stages, so the style-target failure appears to be the first cause rather than a downstream symptom.
---
## What I ruled out
The error originates in webpack `style-loader`'s `insertBySelector` runtime, which throws exactly this message when `document.querySelector(options.insert)` returns null (and, for an iframe target, when `contentDocument.head` is unavailable). I probed each mechanism in isolation, A/B against headless Chrome on an identical constructed DOM. **All behave identically — none of these is the cause:**
| Probed | Result vs Chrome |
| --- | --- |
| `script.onload` property assignment | fires, identical |
| `script.addEventListener('load')` | fires, identical |
| `onload` **content attribute** (cf. #768) | fires, identical |
| dynamic `import()` | resolves, identical |
| `fetch()` | 200/ok, identical |
| `querySelector` / `querySelectorAll`, 23 selector forms (tag, id, class, descendant, child, attribute bare/single/double-quoted, `:root`) | **0 divergences** |
| style-loader's iframe branch: `window.HTMLIFrameElement`, `instanceof`, `contentDocument`, `contentDocument.head`, `appendChild` into it | identical, append succeeds |
| `document.head`, `createElement('style')`, `appendChild`, `insertBefore(s, null)` | all succeed on both |
Also checked and **not** the cause:
- **Not a settle/timing problem.** `--wait 25` with `networkidle0` and `networkidle2` gives byte-identical output to the default.
- **Not bot detection.** Site telemetry reports `isScraperBot:false`, `browserType:green`; `--stealth` changes nothing.
- **Not #699** (page task errors killing the event loop, fixed in `ad29745b`). On a `main` build the `page task error, continuing the event loop` path never fires here — the shell *catches* the style error and reports it via `console.error`, so it was never an uncaught task error.
So the style-target resolution genuinely fails on this page, but not through any primitive I could reproduce standalone — it looks downstream of an earlier divergence inside the vendor's own bundle, which I can't bisect from outside.
## Minor, found while investigating
`--wait-until` silently accepts any value: `obscura fetch https://example.com --dump text --wait-until totalnonsense` exits 0 with no error or warning, apparently falling back to the `load` default. The documented set is `domcontentloaded | load | networkidle0 | networkidle2`. This cost me a round of testing that I believed had exercised `networkidle` but had not. Rejecting unknown values (or warning) would make that failure loud. Happy to split this into its own issue if preferred.Source: h4ckf0r0day/obscura