#817·obscura

Micro-frontend shell never bootstraps: style-loader "Couldn't find a style target" on developer.intuit.com (headless Chrome unaffected)

Author: anthomusCreated Sep 2, 2026Updated Sep 16, 2026
Labelsbug

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

bash
docker run --rm h4ckf0r0day/obscura:latest fetch \
  "https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/customer" \
  --dump text

Public URL, no auth required. Also tried, with byte-identical output:

bash
# valid settle strategies, generous budget
--wait-until networkidle0 --wait 25 --timeout 90
--wait-until networkidle2 --wait 25 --timeout 90
--dump markdown
--stealth

Also 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

bash
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.