mnfst-render 0.5.36 → 0.5.37

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.
Files changed (2) hide show
  1. package/manifest.render.mjs +71 -15
  2. package/package.json +1 -1
@@ -1814,20 +1814,9 @@ function computeGlobalAssetSignature(rootDir) {
1814
1814
  async function waitForVisualSettle(page, { settleMs = 300, mdWaitMs = 5000, capMs = 8000 } = {}) {
1815
1815
  try {
1816
1816
  await page.evaluate(async ({ settleMs, mdWaitMs, capMs }) => {
1817
- // Preferred: the framework's own settled signal (loader ≥0.5.190).
1818
- if (window.__manifestReadyCoordinator) {
1819
- const start = Date.now();
1820
- while (!window.__manifestReady && Date.now() - start < capMs + 8000) {
1821
- await new Promise((r) => setTimeout(r, 100));
1822
- }
1823
- if (document.fonts?.ready) { try { await document.fonts.ready; } catch { /* best-effort */ } }
1824
- await new Promise((r) => {
1825
- const t = setTimeout(r, 250);
1826
- requestAnimationFrame(() => requestAnimationFrame(() => { clearTimeout(t); r(); }));
1827
- });
1828
- return;
1829
- }
1830
- // Fallback heuristic for older framework builds:
1817
+ // The framework's boot signal (loader ≥0.5.190) gates the start, but it
1818
+ // fires once per page — per-ROUTE work is tracked by the live markdown
1819
+ // pending counter and the DOM heuristic below, so all three AND together.
1831
1820
  const state = () => {
1832
1821
  let styleLen = 0;
1833
1822
  for (const s of document.querySelectorAll('style')) styleLen += (s.textContent || '').length;
@@ -1838,14 +1827,58 @@ async function waitForVisualSettle(page, { settleMs = 300, mdWaitMs = 5000, capM
1838
1827
  const sig = `${mdEmpty}:${document.body?.innerText.length || 0}:${styleLen}:${document.fonts ? document.fonts.status : ''}`;
1839
1828
  return { sig, mdEmpty };
1840
1829
  };
1830
+ // Alpine 3.x can strand already-flushed effects when a sibling effect
1831
+ // creates state mid-flush — a route's x-if chain then never stamps until
1832
+ // something re-queues it. Bumping the data store's version re-runs every
1833
+ // store-reading effect: a no-op on settled pages, an unstick on stuck ones.
1834
+ const nudge = () => {
1835
+ try {
1836
+ if (window.Alpine?.store) {
1837
+ const s = window.Alpine.store('data');
1838
+ if (s) window.Alpine.store('data', { ...s, _dataVersion: (s._dataVersion || 0) + 1 });
1839
+ }
1840
+ } catch { /* best-effort */ }
1841
+ };
1842
+ // Responsive/variant utilities (md:row, dark:bg-*) are compiled at
1843
+ // runtime and the compiler throttles recompiles — a late-discovered
1844
+ // variant class leaves the layout in its mobile/stacked state at capture.
1845
+ // Hold while any variant class used in the DOM has no matching CSS rule.
1846
+ const unresolvedVariants = () => {
1847
+ try {
1848
+ const used = new Set();
1849
+ for (const el of document.querySelectorAll('[class*=":"]')) {
1850
+ for (const c of el.classList) if (c.includes(':') && !c.startsWith('[')) used.add(c);
1851
+ if (used.size > 300) break;
1852
+ }
1853
+ if (!used.size) return 0;
1854
+ let selectors = '';
1855
+ for (const sheet of document.styleSheets) {
1856
+ try { for (const r of sheet.cssRules) if (r.selectorText) selectors += r.selectorText + '\n'; } catch { /* cross-origin */ }
1857
+ }
1858
+ let missing = 0;
1859
+ for (const c of used) {
1860
+ if (!selectors.includes('.' + CSS.escape(c))) missing++;
1861
+ }
1862
+ return missing;
1863
+ } catch { return 0; }
1864
+ };
1841
1865
  const start = Date.now();
1842
1866
  let last = state();
1843
1867
  let stableSince = Date.now();
1868
+ let nudged = 0;
1844
1869
  while (Date.now() - start < capMs) {
1845
1870
  await new Promise((r) => setTimeout(r, 100));
1871
+ const elapsed = Date.now() - start;
1872
+ if ((nudged === 0 && elapsed > 300) || (nudged === 1 && elapsed > 2000)) { nudged++; nudge(); }
1846
1873
  const cur = state();
1874
+ const bootPending = !!window.__manifestReadyCoordinator && !window.__manifestReady;
1875
+ const inflight = (window.__manifestMarkdownPending || 0) > 0;
1847
1876
  const mdPending = cur.mdEmpty > 0 && Date.now() - start < mdWaitMs;
1848
- if (cur.sig !== last.sig || mdPending) {
1877
+ const utilPending = elapsed < mdWaitMs + 4000 && unresolvedVariants() > 0;
1878
+ // A throttled compiler can DROP the request that would resolve them —
1879
+ // a class mutation re-triggers its observer's schedule.
1880
+ if (utilPending && elapsed % 500 < 100) document.body.classList.toggle('mnfst-og-nudge');
1881
+ if (cur.sig !== last.sig || bootPending || inflight || mdPending || utilPending) {
1849
1882
  last = cur;
1850
1883
  stableSince = Date.now();
1851
1884
  } else if (Date.now() - stableSince >= settleMs) {
@@ -1953,6 +1986,11 @@ async function takeOgSnapshot(page, outputDir, pathSeg, globalAssetSignature, ca
1953
1986
  // body min-h-screen + flex grow) can otherwise screenshot as blank if
1954
1987
  // the compositor doesn't repaint between setViewport and screenshot.
1955
1988
  await page.evaluate(() => window.scrollTo(0, 0));
1989
+ // Throwaway shot first: it forces the compositor to produce a fresh frame,
1990
+ // so the real capture below reflects the current DOM rather than a stale
1991
+ // surface (headless pages can sit frames behind after heavy updates).
1992
+ await page.screenshot({ type: 'png', clip: { x: 0, y: 0, width: 8, height: 8 }, captureBeyondViewport: false }).catch(() => { });
1993
+ await new Promise((r) => setTimeout(r, 120));
1956
1994
  await page.screenshot({
1957
1995
  path: filePath,
1958
1996
  type: 'png',
@@ -3322,6 +3360,24 @@ async function runPrerender(config) {
3322
3360
  const apply = () => {
3323
3361
  try {
3324
3362
  if (locale && typeof locale === 'string') document.documentElement.lang = locale;
3363
+ // View transitions freeze a snapshot layer over the updated DOM in
3364
+ // headless — OG screenshots then capture stale pre-route pixels.
3365
+ // Router opt-out for SPA swaps, plus a late <style> so the reset's
3366
+ // cross-document @view-transition{navigation:auto} is overridden
3367
+ // (appended after stylesheets; last descriptor wins).
3368
+ document.documentElement.setAttribute('data-no-view-transitions', '');
3369
+ const killCrossDocVT = () => {
3370
+ try {
3371
+ const s = document.createElement('style');
3372
+ s.textContent = '@view-transition { navigation: none }';
3373
+ (document.head || document.documentElement).appendChild(s);
3374
+ } catch { /* no-op */ }
3375
+ };
3376
+ if (document.readyState === 'loading') {
3377
+ document.addEventListener('DOMContentLoaded', killCrossDocVT, { once: true });
3378
+ } else {
3379
+ killCrossDocVT();
3380
+ }
3325
3381
  } catch {
3326
3382
  /* no-op */
3327
3383
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.5.36",
3
+ "version": "0.5.37",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {