Isolated-rendering contexts — audit + the inherit-block trap

2026-05-15 · late morning · post-XSL-fix debrief + portfolio audit

Today’s agent-sitemap.xsl regression (Newsreader → Georgia + white background instead of cream) wasn’t an XSL-specific bug — it was a class of bug. Any presentation-layer syntax that renders in its own document context doesn’t inherit page-level CSS, fonts, or variables from the rest of the site. This report names the class, audits every surface in the portfolio that’s vulnerable, and pins recommendations for going forward.

TL;DR

The pattern

Context type What’s isolated What you have to re-state
XSL stylesheets Entire rendering — browser transforms XML→HTML client-side, applies styles fresh All CSS variables, all @font-face, all colors, all fonts, all spacing
HTML email Email client wraps the content; outer environment is hostile Inline styles only (most clients strip <style>), web-safe fonts, no external CSS, no JS, no CSS variables
iframes Separate document, no parent CSS access Everything — <head>, fonts, scripts
Web Components / Shadow DOM Shadow root isolates styles Inside-shadow styling explicit; CSS custom properties cross the boundary via opt-in only
PDFs (browser print) Different media query Print stylesheet OR equivalent fallback
SVG with <style> When inlined: shares with HTML. When loaded as <img> / external: isolated If external/img: re-state colors, fonts
AMP pages Restricted CSS (no external, size cap, allowed properties) Re-state in <style amp-custom>
RSS feeds (with XSL) Same as XSL stylesheets Same as XSL

The pattern is isolation. The instinct is inheritance (because we’re used to global stylesheets, design tokens, font links). The trap is shipping something that “looks right when previewed in a styled container” — but renders bare in the browser’s actual rendering context.

Today’s incident — what regressed and why

File: ~/Code/dare-co-uk/agent-sitemap.xsl (shipped this morning as part of the agent-discoverability stack)

Symptom (per Dan): “80% great, lost the 20%” — the italic-Newsreader H1 fell back to Georgia, the cream-background brand-feel fell back to white.

Root cause:

/* Original (the bug): */
body { background: #fff; ... }          /* white instead of cream */
.hero h1 { font-family: "Newsreader", Georgia, serif; font-style: italic; }
/* ↑ references Newsreader but no @font-face → falls back to Georgia */

The XSL document doesn’t share resources with dare.co.uk’s main pages. The Newsreader font that loads on every other page wasn’t loaded here. The cream background that’s a :root --cream var on every other page wasn’t defined here.

Fix (commit 22a3ba46 + 29c2dc26):

:root { --cream: #f5f0e8; ... }         /* define the var */
body { background: var(--cream); ... }  /* use it */

/* @font-face blocks added: three unicode-range slices matching
   dare.co.uk's main inline @font-face emissions */
@font-face { font-family: Newsreader; src: url(/cf-fonts/v/newsreader/5.0.16/latin/opsz/italic.woff2); ... }
@font-face { /* latin-ext */ }
@font-face { /* vietnamese */ }

Diagnostic bonus: /cf-fonts/... paths on dare.co.uk return HTTP 200 to browsers (with Referer set) but 404 to raw curl. Cloudflare hotlink protection on the font CDN paths. Not a problem in practice (the XSL is loaded by a browser visiting dare.co.uk) but worth knowing for future curl-based font diagnostics.

Audit — every isolated-rendering surface in the portfolio

XSL stylesheets

File Status Notes
~/Code/dare-co-uk/agent-sitemap.xsl ✓ Fixed today @font-face for Newsreader (via /cf-fonts/) + cream background restored
~/Code/dare-co-uk/main-sitemap.xsl ✓ Healthy Loads Newsreader via Google Fonts <link> (different mechanism, same effect). Background uses --bg: #fbf8f3 (slightly warmer off-white than archive cream) — intentional
~/Downloads/dogwood-agent-stack-staging/agent-sitemap.xsl ✓ Robust by construction Uses Georgia primary, Newsreader as fallback. Georgia is a system font (always available) so Newsreader never loads — but it’s never needed

Email templates (Worker)

File Status Notes
~/Code/dare-contact-page/dare-contact-worker.js::buildEmailHtml() ✓ Email-correct Uses -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif — system stack. No webfonts (correct for email clients which often strip them). Background colors are explicit hex (#f9f9f9, etc.) — no var reliance

Email is the most isolated rendering context in existence (Gmail strips half your <style> block) and the Worker template is correctly defensive — inline styles, system fonts, plain hex colors. Good model for any future portfolio email work.

iframes / web components / PDFs

None in current portfolio. Watch item: if dogwood ever embeds a third-party booking widget via iframe, or audrey adds a custom Shopify section with Web Components, the iframe / shadow-DOM rules apply (re-state styling inside the boundary).

Future / planned surfaces

The font-loading inconsistency (latent confusion source)

dare.co.uk currently loads Newsreader via two different mechanisms, both correct, both working:

<!-- Pattern A — Google Fonts <link> (used in main-sitemap.xsl + 8 article pages) -->
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@..." rel="stylesheet">

<!-- Pattern B — inline @font-face to Cloudflare-hosted woff2 (used in homepage, agent-sitemap.xsl) -->
<style>
  @font-face { font-family: Newsreader; src: url(/cf-fonts/v/newsreader/.../italic.woff2); ... }
</style>

Both produce the same visual outcome. The performance trade-off: - Pattern A (Google Fonts) — easier to maintain, single line, but external request adds DNS + handshake latency - Pattern B (Cloudflare-hosted) — faster (same-origin, edge-cached), but more verbose, depends on Cloudflare Fonts feature being enabled on the zone

Recommendation: standardise on Pattern B (Cloudflare-hosted @font-face) for consistency with the homepage. Migrate main-sitemap.xsl + the 8 article pages over time. NOT urgent — both work today. Worth pinning in a follow-up cleanup pass.

Discovery checklist — for any new isolated-rendering surface

When adding a new XSL stylesheet, email template, Shadow-DOM component, iframe page, or anything-else-rendered-in-isolation, run through this checklist BEFORE shipping:

  1. Fonts — are all font-family references backed by an explicit @font-face, <link>, or system-font-only stack? Test in an incognito window (no font cache)
  2. Colors — are all var(--...) references defined locally in this document’s CSS? Test by deleting one var and confirming the fallback is what you want
  3. Layout fundamentals — is box-sizing: border-box, the body reset, the viewport meta in place? Don’t assume the surrounding doc has them
  4. Cross-domain resources — does anything reference an external URL that has hotlink protection (image CDNs, font CDNs)? Test with a Referer header that matches production
  5. Print / dark mode — does the surface need to support @media print or @media (prefers-color-scheme: dark)? Email clients especially have dark-mode quirks
  6. Browser-render preview — open the file directly in a browser (not in a styled wrapper) before declaring it shipped. If it looks naked, it’s the bug
  7. Hard-reload verification — after deploy, hard-reload (Cmd+Shift+R or DevTools “Disable cache”) to bypass font/CSS caching

Recommendations + watch items

Immediate

Near-term

Long-term watch

What this earns the portfolio

Linked artefacts


Generated 2026-05-15 after Dan flagged “80% great, lost the 20%” on the live sitemap page. The 20% is now back. The class of bug is named.

Source: dare_strategy_isolated_rendering_contexts_2026-05-15.md · Rendered 2026-05-15 11:42