From e505f12b102f14fef18230d6a16959881a196d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sun, 3 May 2026 22:56:13 +0200 Subject: [PATCH] docs: stop hiding snippet fallback prematurely (`:defined` race) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reports of "no snippets visible" on the live site, despite the page shipping 9 elements + 9
blocks: the fallback was hidden by a CSS rule:

    php-snippet:defined .snippet-fallback { display: none; }

`:defined` matches the moment `customElements.define('php-snippet', ...)`
runs — i.e. immediately when Playground's php-code-snippet.js is parsed —
*before* the snippet's shadow DOM has any content. So in the gap between
"custom element registered" and "shadow DOM rendered with code + Run
button", the fallback was already gone and the widget hadn't drawn yet.
On a slow client (or one where a later step in the Playground module
fails) the user sees a permanent blank gap.

Fix: drop the `:defined` rule from style.css and hide the fallback
explicitly from page.js's patchSnippet, which only runs once
`shadowRoot.querySelector('pre code')` has actually returned a node.
By that point the snippet's UI is on screen, so swapping the fallback
out is safe. If the cross-origin module never loads, patchSnippet is
never reached and the fallback stays visible.

Verified by rendering the local site twice with chromium:
  * Playground module mapped to 127.0.0.1:1 (unreachable) → fallback
    
 visible for every snippet, code readable.
  * Module reachable → custom element renders the editable code area
    + Run button, fallback hidden.

Asset version bumped (20260503-script-unescape → 20260503-fallback-
explicit-hide) so caches refresh.

87/87 snippets still match captured stdout.
---
 bin/build-reference.py |  2 +-
 docs/assets/page.js    |  9 +++++++++
 docs/assets/style.css  | 12 +++++++-----
 3 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/bin/build-reference.py b/bin/build-reference.py
index 2a5a29c93..f768f95ac 100644
--- a/bin/build-reference.py
+++ b/bin/build-reference.py
@@ -16,7 +16,7 @@
 from _load_catalog import load_components_rich
 
 DOCS = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'docs', 'reference')
-ASSET_VERSION = '20260503-script-unescape'
+ASSET_VERSION = '20260503-fallback-explicit-hide'
 
 
 PAGE_HEAD = '''
diff --git a/docs/assets/page.js b/docs/assets/page.js
index 671003028..ef05e4804 100644
--- a/docs/assets/page.js
+++ b/docs/assets/page.js
@@ -104,6 +104,15 @@
 		const code = root.querySelector('pre code');
 		if (!code) return;
 
+		// Shadow DOM has rendered content. Hide the static fallback now —
+		// not via a CSS `:defined` rule, which fires too early (the moment
+		// customElements.define() runs, before shadow-DOM render). See
+		// .snippet-fallback rules in style.css for context.
+		const fallback = el.querySelector(':scope > .snippet-fallback');
+		if (fallback) {
+			fallback.style.display = 'none';
+		}
+
 		// HTML emitted by build-reference.py escapes " wrapper as "<\/script" so the
 		// outer tag isn't closed prematurely. The browser preserves that
diff --git a/docs/assets/style.css b/docs/assets/style.css
index eb429cc66..eb8f81d36 100644
--- a/docs/assets/style.css
+++ b/docs/assets/style.css
@@ -483,10 +483,13 @@ p code, li code, td code {
 
 php-snippet { display: block; margin: 1rem 0 1.75rem; }
 
-/* Static fallback: shows the snippet code if the cross-origin Playground
-   custom element fails to register (CSP block, slow network, adblocker,
-   no-JS reader). Hidden as soon as  is defined so the
-   interactive widget owns the screen when it boots. */
+/* Static fallback: shows the snippet code until the cross-origin Playground
+   custom element registers AND populates its shadow DOM. page.js hides
+   this element explicitly once shadow-DOM render is confirmed in
+   patchSnippet — we do not use a CSS `:defined` rule because that fires
+   the instant `customElements.define()` runs, before the shadow DOM has
+   any content. If the cross-origin module never loads (CSP, adblock,
+   slow network, no-JS), this fallback stays visible. */
 .snippet-fallback {
 	margin: 0;
 	padding: 0.9rem;
@@ -499,7 +502,6 @@ php-snippet { display: block; margin: 1rem 0 1.75rem; }
 	line-height: 1.55;
 }
 .snippet-fallback code { color: inherit; background: none; padding: 0; }
-php-snippet:defined .snippet-fallback { display: none; }
 
 .code-example {
 	margin: 1rem 0 1.75rem;