blume 1.5.2 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # blume
2
2
 
3
+ ## 1.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - b6770b3: Fix fully unstyled frames during client-router navigations. Astro hoists the CSS of components rendered after the head has streamed (a page's MDX content, for one) into the body as stylesheet links, and the client router neither preloads nor persists body stylesheets — so swapping in a page painted a frame or two with no CSS applied before its sheet loaded. The layouts now load an incoming page's body stylesheets into the head and wait for them before the swap, and keep the loaded copy across it.
8
+
3
9
  ## 1.5.2
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "Documentation that's fast, AI-ready, and zero-config.",
5
5
  "keywords": [
6
6
  "astro",
@@ -40,7 +40,11 @@ import Analytics from "./Analytics.astro";
40
40
  import Banner from "./Banner.astro";
41
41
  import Favicon from "./Favicon.astro";
42
42
  import Fonts from "./Fonts.astro";
43
- import { BANNER_INIT_SCRIPT, THEME_INIT_SCRIPT } from "./head-scripts.ts";
43
+ import {
44
+ BANNER_INIT_SCRIPT,
45
+ SWAP_STYLESHEET_INIT_SCRIPT,
46
+ THEME_INIT_SCRIPT,
47
+ } from "./head-scripts.ts";
44
48
  import Header from "./Header.astro";
45
49
  import { currentTabForRoute } from "./nav-utils.ts";
46
50
  import WebMcp from "./WebMcp.astro";
@@ -235,6 +239,9 @@ const bannerKey = banner?.dismissible ? banner.key : null;
235
239
  swap the DOM in place instead of tearing the document down, so no
236
240
  browser paints a blank frame between pages. */}
237
241
  <ClientRouter />
242
+ {/* Loads a next page's body-hoisted CSS before the router swaps it in —
243
+ see SWAP_STYLESHEET_INIT_SCRIPT for the unstyled-frame failure mode. */}
244
+ <script is:inline set:html={SWAP_STYLESHEET_INIT_SCRIPT} />
238
245
  <title>{pageTitle}</title>
239
246
  <Favicon appleIcon={appleIcon} favicon={favicon} />
240
247
  <Fonts cssVars={fontCssVars ?? []} />
@@ -33,6 +33,7 @@ import Fonts from "./Fonts.astro";
33
33
  import {
34
34
  BANNER_INIT_SCRIPT,
35
35
  SIDEBAR_SCROLL_INIT_SCRIPT,
36
+ SWAP_STYLESHEET_INIT_SCRIPT,
36
37
  THEME_INIT_SCRIPT,
37
38
  } from "./head-scripts.ts";
38
39
  import Header from "./Header.astro";
@@ -414,6 +415,9 @@ const bannerKey = banner?.dismissible ? banner.key : null;
414
415
  supported, with Astro's simulated fade elsewhere; pairs with the
415
416
  prefetch option in the generated Astro config. */}
416
417
  <ClientRouter />
418
+ {/* Loads a next page's body-hoisted CSS before the router swaps it in —
419
+ see SWAP_STYLESHEET_INIT_SCRIPT for the unstyled-frame failure mode. */}
420
+ <script is:inline set:html={SWAP_STYLESHEET_INIT_SCRIPT} />
417
421
  <title>{pageTitle}</title>
418
422
  <Favicon favicon={favicon} appleIcon={appleIcon} />
419
423
  <Fonts cssVars={fontCssVars ?? []} />
@@ -41,6 +41,29 @@ export const THEME_INIT_SCRIPT = `(()=>{const m=document.currentScript?.dataset.
41
41
  */
42
42
  export const BANNER_INIT_SCRIPT = `(()=>{const k=document.currentScript?.dataset.key;if(!k){return;}const apply=()=>{if(localStorage.getItem("blume-banner:"+k)){document.documentElement.setAttribute("data-blume-banner-hidden","");}};apply();document.addEventListener("astro:after-swap",apply);})();`;
43
43
 
44
+ /**
45
+ * Keep the page styled across client-router swaps. Astro hoists the CSS of a
46
+ * component rendered after the head has streamed (the page's MDX content, the
47
+ * WebMcp island) into the **body** as `<link rel="stylesheet">` tags — and the
48
+ * client router only preloads and persists stylesheets it finds in the head.
49
+ * A swapped-in body `<link>` applies asynchronously, so every navigation to a
50
+ * page with body CSS painted one or two completely unstyled frames (giant raw
51
+ * SVG logo, default link colors) before the sheet kicked in — even when the
52
+ * same sheet was already loaded on the outgoing page, because the swap throws
53
+ * the old body (and its link element) away.
54
+ *
55
+ * Two listeners close the gap. `astro:before-preparation` wraps the router's
56
+ * loader: after the next document is fetched, any of its body stylesheets not
57
+ * already in the live head are appended there and awaited, so their rules
58
+ * apply before the swap. `astro:before-swap` then moves the incoming
59
+ * document's body stylesheets into its head, where the router's head diff
60
+ * keeps the already-loaded copy (matched by `href`) instead of re-inserting a
61
+ * fresh, not-yet-applied link — and drops it again on a later navigation to a
62
+ * page that doesn't use it. A sheet that fails to load resolves rather than
63
+ * wedging the navigation; the page renders as it would have without this.
64
+ */
65
+ export const SWAP_STYLESHEET_INIT_SCRIPT = `(()=>{const sel='body link[rel="stylesheet"]';document.addEventListener("astro:before-preparation",(e)=>{const load=e.loader;e.loader=async()=>{await load();const links=[...e.newDocument.querySelectorAll(sel)].filter((l)=>!document.head.querySelector('link[rel="stylesheet"][href="'+l.getAttribute("href")+'"]'));await Promise.all(links.map((l)=>new Promise((done)=>{const c=document.createElement("link");for(const a of l.attributes){c.setAttribute(a.name,a.value);}c.onload=done;c.onerror=done;document.head.append(c);})));};});document.addEventListener("astro:before-swap",(e)=>{for(const l of e.newDocument.querySelectorAll(sel)){e.newDocument.head.append(l);}});})();`;
66
+
44
67
  /**
45
68
  * Keep the sidebar's scroll useful across page changes. The sidebar is its own
46
69
  * scroll container, reborn scrolled to the top whenever its markup is rebuilt —