pattern-dump 0.1.0-alpha.0 → 0.1.0-alpha.2

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.
@@ -1,26 +1,19 @@
1
1
  // ../../packages/contracts/src/dev-protocol.ts
2
- var SCOPE_BOUNDARY = `## Scope \u2014 the boundary is part of the artifact
3
-
4
- **Covers** \u2014 machine-detectable, static, single-render (no browser layout):
5
- - **HTML content-model** (html-validate) \u2014 nesting/permitted-content violations.
6
- - **Structural a11y** (axe, alwaysOn tier) \u2014 labels, names, ARIA, roles, image-alt.
7
- - **Schema** \u2014 required/shape violations against \`component.schema.json\`.
8
- - **Render outcome** \u2014 blank / error / ok (metadata, never itself a defect).
9
-
10
- **Deliberately NOT covered \u2014 named boundaries, not misses:**
11
- - **Rendered-contrast (axe needsRendering tier)** \u2014 contrast, aria-hidden-focus, duplicate-id-aria.
12
- These require real browser layout/paint; the static jsdom sweep cannot produce them. The
13
- per-component panel shows them (live iframe); this aggregate does not. Fewer rule types here is a
14
- scope boundary, not a gap.
15
- - **Structural/graph findings** \u2014 dead subtrees, a shared-base "component", flattened variant families.
16
- Those are the style graph's lens, not this one.
17
- - **PD reporting gaps** \u2014 PD under-claiming (e.g. missing provenance) is not a subject defect.
18
- - **Interactive / behavioural** \u2014 focus traps, keyboard flow, escape handling. Single static render only.
19
- - **Data-masked** \u2014 a component that renders empty for want of data won't throw the violations it would
20
- when populated; findings are annotated with render outcome wherever it is blank.
21
-
22
- A component with zero findings is **not "clean" \u2014 it is "clean of static machine-detectable defects."**
23
- That is the honest claim; never "clean".`;
2
+ var AUDIT_SCOPE = {
3
+ covered: [
4
+ { name: "HTML content model", detail: "invalid nesting and content that isn't permitted where it appears (html-validate)." },
5
+ { name: "Structural accessibility", detail: "labels, accessible names, ARIA attributes, roles and image alternatives (the axe-core rules that don't need layout)." },
6
+ { name: "Schema", detail: "missing required fields and values of the wrong type, checked against each component's `<name>.schema.json`." },
7
+ { name: "Render outcome", detail: "whether each component rendered, rendered empty or failed. It is reported with the component, not counted as a defect." }
8
+ ],
9
+ notCovered: [
10
+ { name: "Checks that need a rendered page", detail: "colour contrast, focusable content hidden with aria-hidden, and duplicate ids referenced by ARIA. They need real browser layout; the Console's Diagnostics tab runs them for the component on screen." },
11
+ { name: "Interaction", detail: "focus traps, keyboard flow and Escape handling. The audit looks at a single static render." },
12
+ { name: "Structure", detail: "unused components, shared bases and near-duplicate copies. The Relationships tab and `pd export` cover those." },
13
+ { name: "Components that need data", detail: "a component that renders empty without data can't show the problems it would have with data. Its findings carry the render outcome." }
14
+ ],
15
+ note: "No findings means no static, machine-detectable defects, not that a component is fully accessible or correct."
16
+ };
24
17
  function buildAuditRollup(graph, opts = {}) {
25
18
  const total = graph.components.length;
26
19
  const affected = new Set(graph.occurrences.map((o) => o.component));
@@ -62,7 +55,7 @@ function buildAuditRollup(graph, opts = {}) {
62
55
  renderOutcomes: { ok: outcomes["ok"] ?? 0, blank: outcomes["blank"] ?? 0, error: outcomes["error"] ?? 0 },
63
56
  rulesByBlast,
64
57
  perComponent,
65
- scope: SCOPE_BOUNDARY
58
+ scope: AUDIT_SCOPE
66
59
  };
67
60
  }
68
61
  function previewUrl(id, variant, theme) {
@@ -237,8 +230,7 @@ var session = {
237
230
  dataCache: null,
238
231
  dataRaw: null,
239
232
  renderedA11y: [],
240
- scanPending: false,
241
- lastPdWidth: null
233
+ scanPending: false
242
234
  };
243
235
 
244
236
  // src/core/runtime.ts
@@ -388,9 +380,9 @@ function createStore() {
388
380
  function notify() {
389
381
  for (const fn of listeners) fn();
390
382
  }
391
- function persist(state) {
383
+ function persist(state2) {
392
384
  try {
393
- localStorage.setItem(STORAGE_KEY, toJSON(state));
385
+ localStorage.setItem(STORAGE_KEY, toJSON(state2));
394
386
  } catch {
395
387
  }
396
388
  }
@@ -796,7 +788,7 @@ async function runRenderedA11yScan() {
796
788
  }
797
789
  const axeWindow = dom.iframe.contentWindow;
798
790
  if (!axeWindow.axe) await injectAxe();
799
- const root = dom.iframe.contentDocument.querySelector("main") ?? dom.iframe.contentDocument.body;
791
+ const root = dom.iframe.contentDocument.body;
800
792
  const results = await axeWindow.axe.run(root, { runOnly: session.needsRenderingRules });
801
793
  if (gen !== scanGen) return;
802
794
  session.renderedA11y = mapAxeResults(results);
@@ -983,6 +975,12 @@ var el = (tag, cls, text) => {
983
975
  if (text !== void 0) n.textContent = text;
984
976
  return n;
985
977
  };
978
+ function appendWithCode(parent, text) {
979
+ text.split("`").forEach((part, i) => {
980
+ if (part === "") return;
981
+ parent.appendChild(i % 2 === 1 ? el("code", "audit-scope-code", part) : document.createTextNode(part));
982
+ });
983
+ }
986
984
  function tagBadge(tag) {
987
985
  const s = el("span", "audit-tag", `[${tag}]`);
988
986
  s.dataset.tag = tag;
@@ -1060,8 +1058,22 @@ function renderAuditView(rollup) {
1060
1058
  }
1061
1059
  const scope = el("details", "audit-scope");
1062
1060
  scope.open = true;
1063
- scope.appendChild(el("summary", "audit-scope-summary", "Scope \u2014 what this count does and does not cover"));
1064
- scope.appendChild(el("pre", "audit-scope-body", rollup.scope));
1061
+ scope.appendChild(el("summary", "audit-scope-summary", "Scope: what this count does and does not cover"));
1062
+ const scopeList = (label, items, kind) => {
1063
+ scope.appendChild(el("p", "audit-scope-label", label));
1064
+ const list = el("ul", "audit-scope-list");
1065
+ list.dataset.scope = kind;
1066
+ for (const item of items) {
1067
+ const li = el("li", "audit-scope-item");
1068
+ li.appendChild(el("strong", "audit-scope-name", `${item.name}: `));
1069
+ appendWithCode(li, item.detail);
1070
+ list.appendChild(li);
1071
+ }
1072
+ scope.appendChild(list);
1073
+ };
1074
+ scopeList("Covered: static checks on a single render, without browser layout.", rollup.scope.covered, "covered");
1075
+ scopeList("Not covered:", rollup.scope.notCovered, "not-covered");
1076
+ scope.appendChild(el("p", "audit-scope-note", rollup.scope.note));
1065
1077
  section.appendChild(scope);
1066
1078
  return section;
1067
1079
  }
@@ -1708,6 +1720,175 @@ function withForwarder(doc) {
1708
1720
  return doc.includes("</body>") ? doc.replace("</body>", FORWARDER + "</body>") : doc + FORWARDER;
1709
1721
  }
1710
1722
 
1723
+ // src/stage/preview-sizing.ts
1724
+ function setVpProp(el2, prop, value) {
1725
+ const cur = parseFloat(el2.style.getPropertyValue(prop)) || 0;
1726
+ if (Math.abs(value - cur) > 1) {
1727
+ el2.style.setProperty(prop, `${value}px`);
1728
+ }
1729
+ }
1730
+
1731
+ // src/stage/content-height.ts
1732
+ var GROWTH_FACTOR = 1.5;
1733
+ var GROWTH_RUN = 2;
1734
+ var state = /* @__PURE__ */ new WeakMap();
1735
+ var circular = /* @__PURE__ */ new WeakSet();
1736
+ var report = null;
1737
+ function isCircularGrowth(previous, measured) {
1738
+ if (previous === void 0) return false;
1739
+ return measured > previous.last * GROWTH_FACTOR && previous.run + 1 >= GROWTH_RUN;
1740
+ }
1741
+ function fitsHeight(frame) {
1742
+ return frame.closest('[data-layout="full"]') === null;
1743
+ }
1744
+ function acceptContentHeight(frame, height) {
1745
+ if (!fitsHeight(frame) || circular.has(frame)) return false;
1746
+ const previous = state.get(frame);
1747
+ if (isCircularGrowth(previous, height)) {
1748
+ circular.add(frame);
1749
+ state.delete(frame);
1750
+ frame.dataset.pdHeight = "circular";
1751
+ setVpProp(frame, "--vp-h", previous.steady);
1752
+ report?.(frame);
1753
+ return true;
1754
+ }
1755
+ const grew = previous !== void 0 && height > previous.last * GROWTH_FACTOR;
1756
+ state.set(frame, {
1757
+ last: height,
1758
+ steady: grew ? previous.steady : height,
1759
+ run: grew ? previous.run + 1 : 0
1760
+ });
1761
+ setVpProp(frame, "--vp-h", height);
1762
+ return true;
1763
+ }
1764
+ function resetContentHeight(frame) {
1765
+ state.delete(frame);
1766
+ circular.delete(frame);
1767
+ delete frame.dataset.pdHeight;
1768
+ }
1769
+ function initContentHeight(onCircular) {
1770
+ report = onCircular;
1771
+ }
1772
+
1773
+ // src/stage/content-width.ts
1774
+ var measuring = /* @__PURE__ */ new WeakMap();
1775
+ var fitted = /* @__PURE__ */ new WeakMap();
1776
+ function fitsContent(frame) {
1777
+ if (frame === dom.iframe) {
1778
+ const vp = store.url.viewport;
1779
+ return (vp === "fit" || vp === "content") && dom.stageView.dataset.layout !== "full";
1780
+ }
1781
+ return frame.closest(".gallery-item") !== null;
1782
+ }
1783
+ function measureContentWidth(frame) {
1784
+ if (!fitsContent(frame)) {
1785
+ stopMeasuring(frame);
1786
+ return;
1787
+ }
1788
+ frame.classList.add("vp-measuring");
1789
+ frame.style.removeProperty("--vp-w");
1790
+ fitted.delete(frame);
1791
+ measuring.set(frame, frame.clientWidth);
1792
+ }
1793
+ function stopMeasuring(frame) {
1794
+ measuring.delete(frame);
1795
+ fitted.delete(frame);
1796
+ frame.classList.remove("vp-measuring");
1797
+ }
1798
+ function remeasureAll() {
1799
+ for (const frame of document.querySelectorAll("#preview-frame, .gallery-item iframe")) {
1800
+ measureContentWidth(frame);
1801
+ }
1802
+ }
1803
+ function widthThresholds(condition) {
1804
+ const out = [];
1805
+ const px = (n, unit) => parseFloat(n) * (unit.toLowerCase() === "px" ? 1 : 16);
1806
+ for (const query of condition.split(",")) {
1807
+ if (/^\s*(only\s+)?print\b/i.test(query)) continue;
1808
+ for (const m of query.matchAll(/(?:min|max)-(?:width|inline-size)\s*:\s*(-?[\d.]+)(px|r?em)\b/gi)) out.push(px(m[1], m[2]));
1809
+ for (const m of query.matchAll(/(?:width|inline-size)\s*[<>]=?\s*(-?[\d.]+)(px|r?em)\b/gi)) out.push(px(m[1], m[2]));
1810
+ for (const m of query.matchAll(/(-?[\d.]+)(px|r?em)\s*[<>]=?\s*(?:width|inline-size)\b/gi)) out.push(px(m[1], m[2]));
1811
+ }
1812
+ return out;
1813
+ }
1814
+ function widthConditionsChange(frame, narrow, wide) {
1815
+ const doc = frame.contentDocument;
1816
+ if (!doc) return false;
1817
+ const between = (t) => t >= narrow && t <= wide;
1818
+ const matches = (selector) => {
1819
+ try {
1820
+ return doc.querySelector(selector) !== null;
1821
+ } catch {
1822
+ return true;
1823
+ }
1824
+ };
1825
+ const stylesThePreview = (rules) => {
1826
+ for (const rule of rules ?? []) {
1827
+ const r = rule;
1828
+ if (r.selectorText !== void 0 && matches(r.selectorText)) return true;
1829
+ if (r.cssRules !== void 0 && stylesThePreview(r.cssRules)) return true;
1830
+ }
1831
+ return false;
1832
+ };
1833
+ const visit = (rules) => {
1834
+ for (const rule of rules ?? []) {
1835
+ const r = rule;
1836
+ const condition = r.styleSheet === void 0 && r.media !== void 0 ? r.media.mediaText : r.containerName !== void 0 ? r.conditionText ?? null : null;
1837
+ if (condition !== null && widthThresholds(condition).some(between) && stylesThePreview(r.cssRules)) return true;
1838
+ if (r.styleSheet !== void 0) {
1839
+ const imported = sheetRules(r.styleSheet);
1840
+ if (r.media && widthThresholds(r.media.mediaText).some(between) && stylesThePreview(imported)) return true;
1841
+ if (visit(imported)) return true;
1842
+ } else if (r.cssRules !== void 0 && visit(r.cssRules)) return true;
1843
+ }
1844
+ return false;
1845
+ };
1846
+ return [...doc.styleSheets].some((sheet) => visit(sheetRules(sheet)));
1847
+ }
1848
+ function sheetRules(sheet) {
1849
+ try {
1850
+ return sheet?.cssRules;
1851
+ } catch {
1852
+ return void 0;
1853
+ }
1854
+ }
1855
+ function setWidthNow(frame, width) {
1856
+ frame.classList.add("vp-measuring");
1857
+ if (width === null) frame.style.removeProperty("--vp-w");
1858
+ else setVpProp(frame, "--vp-w", width);
1859
+ requestAnimationFrame(() => requestAnimationFrame(() => frame.classList.remove("vp-measuring")));
1860
+ }
1861
+ function acceptContentWidth(frame, width, at, scroll) {
1862
+ const full = measuring.get(frame);
1863
+ if (full === void 0) {
1864
+ const state2 = fitted.get(frame);
1865
+ if (state2 === void 0 || scroll <= at + 1) return false;
1866
+ const parentWidth = frame.parentElement?.clientWidth ?? Infinity;
1867
+ if (state2 === "measured" && scroll < parentWidth) {
1868
+ fitted.set(frame, "widened");
1869
+ setWidthNow(frame, scroll);
1870
+ } else {
1871
+ fitted.delete(frame);
1872
+ setWidthNow(frame, null);
1873
+ }
1874
+ return true;
1875
+ }
1876
+ if (Math.abs(at - full) > 1) return false;
1877
+ measuring.delete(frame);
1878
+ const narrower = width < full - 1 && !widthConditionsChange(frame, width, full);
1879
+ if (narrower) fitted.set(frame, "measured");
1880
+ setWidthNow(frame, narrower ? width : null);
1881
+ return true;
1882
+ }
1883
+ function initContentWidth() {
1884
+ let theme = store.local.theme;
1885
+ store.subscribe(() => {
1886
+ if (store.local.theme === theme) return;
1887
+ theme = store.local.theme;
1888
+ remeasureAll();
1889
+ });
1890
+ }
1891
+
1711
1892
  // src/stage/viewport.ts
1712
1893
  var PRESETS = {
1713
1894
  mobile: { w: 375, h: 667 },
@@ -1755,7 +1936,7 @@ function syncMarks() {
1755
1936
  }
1756
1937
  const sr = dom.stageView.getBoundingClientRect();
1757
1938
  const EPSILON = 2;
1758
- if (fr.width >= sr.width - EPSILON || fr.height >= sr.height - EPSILON) {
1939
+ if (dom.iframe.classList.contains("vp-fit") || fr.width >= sr.width - EPSILON || fr.height >= sr.height - EPSILON) {
1759
1940
  dom.markOverlay.hidden = true;
1760
1941
  return;
1761
1942
  }
@@ -1784,19 +1965,14 @@ function applyViewport() {
1784
1965
  dom.iframe.classList.remove("vp-sized");
1785
1966
  dom.iframe.classList.remove("vp-content");
1786
1967
  dom.iframe.classList.add("vp-fit");
1968
+ measureContentWidth(dom.iframe);
1787
1969
  } else if (vp === "content") {
1788
- const layout = dom.stageView.getAttribute("data-layout") ?? "centered";
1789
- if (session.lastPdWidth !== null) {
1790
- dom.iframe.style.setProperty("--vp-w", `${session.lastPdWidth}px`);
1791
- } else if (layout === "full") {
1792
- dom.iframe.style.removeProperty("--vp-w");
1793
- } else {
1794
- dom.iframe.style.setProperty("--vp-w", "max-content");
1795
- }
1796
1970
  dom.iframe.classList.remove("vp-sized");
1797
1971
  dom.iframe.classList.remove("vp-fit");
1798
1972
  dom.iframe.classList.add("vp-content");
1973
+ measureContentWidth(dom.iframe);
1799
1974
  } else {
1975
+ stopMeasuring(dom.iframe);
1800
1976
  const p = PRESETS[vp];
1801
1977
  dom.iframe.style.setProperty("--vp-w", `${p.w}px`);
1802
1978
  dom.iframe.classList.add("vp-sized");
@@ -1832,6 +2008,7 @@ function stopDisco() {
1832
2008
  function startDisco() {
1833
2009
  if (vpDisco) return;
1834
2010
  vpDisco = true;
2011
+ stopMeasuring(dom.iframe);
1835
2012
  dom.viewportToolbar.setAttribute("data-disco-active", "");
1836
2013
  dom.vpDiscoBtn.setAttribute("aria-pressed", "true");
1837
2014
  dom.vpDiscoBtn.innerHTML = svgIcon("vp-disco-fill");
@@ -1918,6 +2095,8 @@ function initViewport(onFullscreen) {
1918
2095
  iframeObserver.observe(dom.iframe);
1919
2096
  iframeObserver.observe(dom.stageView);
1920
2097
  dom.iframe.addEventListener("load", () => {
2098
+ resetContentHeight(dom.iframe);
2099
+ measureContentWidth(dom.iframe);
1921
2100
  updateReadout();
1922
2101
  syncFrameFocusListeners();
1923
2102
  });
@@ -2190,7 +2369,9 @@ async function buildVariantsGallery() {
2190
2369
  f.addEventListener("load", () => {
2191
2370
  if (store.local.theme) applyThemeToIframe(f, store.local.theme);
2192
2371
  const h = f.contentDocument?.documentElement?.scrollHeight;
2372
+ resetContentHeight(f);
2193
2373
  if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
2374
+ measureContentWidth(f);
2194
2375
  });
2195
2376
  fig.appendChild(h2);
2196
2377
  fig.appendChild(f);
@@ -2230,7 +2411,9 @@ function renderOverview(overview) {
2230
2411
  f.addEventListener("load", () => {
2231
2412
  if (store.local.theme) applyThemeToIframe(f, store.local.theme);
2232
2413
  const h = f.contentDocument?.documentElement?.scrollHeight;
2414
+ resetContentHeight(f);
2233
2415
  if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
2416
+ measureContentWidth(f);
2234
2417
  });
2235
2418
  f.src = resourceUrl(previewUrl(item.id, void 0, store.local.theme || void 0));
2236
2419
  fig.appendChild(h2);
@@ -2283,7 +2466,6 @@ async function applyComponent(detail, variant) {
2283
2466
  dom.iframe.srcdoc = _html;
2284
2467
  });
2285
2468
  dom.iframe.style.removeProperty("--vp-h");
2286
- session.lastPdWidth = null;
2287
2469
  if (isDiscoRunning()) stopDisco();
2288
2470
  applyViewport();
2289
2471
  delete dom.dockEl.dataset.mode;
@@ -2346,33 +2528,98 @@ async function navigate(componentId, variant) {
2346
2528
  await applyComponent(detail, variant);
2347
2529
  } catch (e) {
2348
2530
  console.error("[pd] Navigate error:", e);
2531
+ showStageMessage(`Could not load \u201C${componentId}\u201D: ${e instanceof Error ? e.message : String(e)}`);
2532
+ }
2533
+ }
2534
+ async function navigateUnknown(path, variant) {
2535
+ const res = await fetch(componentUrl(path, variant)).catch(() => null);
2536
+ if (res?.ok) {
2537
+ await applyComponent(await res.json(), variant);
2538
+ return;
2349
2539
  }
2540
+ showStageMessage(`No component or folder \u201C${path}\u201D.`, closestPaths(path));
2541
+ }
2542
+ function closestPaths(path) {
2543
+ const all = [];
2544
+ const walk = (nodes) => {
2545
+ for (const n of nodes) {
2546
+ all.push(n.path);
2547
+ if (n.children) walk(n.children);
2548
+ }
2549
+ };
2550
+ walk(session.tree.roots);
2551
+ const name = path.split("/").pop() ?? path;
2552
+ const sameName = all.filter((p) => p.split("/").pop() === name);
2553
+ const near = all.map((p) => ({ p, d: editDistance(p, path) })).filter(({ p, d }) => d <= Math.max(2, Math.floor(path.length / 5)) && !sameName.includes(p)).sort((a, b) => a.d - b.d).map(({ p }) => p);
2554
+ return [...sameName, ...near].slice(0, 3);
2555
+ }
2556
+ function editDistance(a, b) {
2557
+ const row = Array.from({ length: b.length + 1 }, (_, i) => i);
2558
+ for (let i = 1; i <= a.length; i++) {
2559
+ let prev = row[0];
2560
+ row[0] = i;
2561
+ for (let j = 1; j <= b.length; j++) {
2562
+ const cur = row[j];
2563
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
2564
+ prev = cur;
2565
+ }
2566
+ }
2567
+ return row[b.length];
2568
+ }
2569
+ function showFolderArea(activePath) {
2570
+ session.current = null;
2571
+ session.currentVariant = null;
2572
+ session.renderedHtmlCache = null;
2573
+ session.rawRenderedHtml = null;
2574
+ session.dataCache = null;
2575
+ session.dataRaw = null;
2576
+ session.stageMode = "single";
2577
+ dom.variantBar.hidden = true;
2578
+ dom.panel.hidden = true;
2579
+ dom.singlePreview.hidden = true;
2580
+ dom.categoryOverview.hidden = false;
2581
+ dom.variantsGalleryEl.hidden = true;
2582
+ dom.stageView.removeAttribute("data-layout");
2583
+ dom.dockEl.dataset.mode = "overview";
2584
+ switchDockTab("console");
2585
+ setActiveTreeItem(activePath);
2586
+ }
2587
+ function showStageMessage(message, suggestions = []) {
2588
+ showFolderArea("");
2589
+ dom.categoryOverview.innerHTML = "";
2590
+ const box = document.createElement("div");
2591
+ box.className = "stage-message";
2592
+ box.setAttribute("role", "status");
2593
+ const p = document.createElement("p");
2594
+ p.textContent = message;
2595
+ box.appendChild(p);
2596
+ if (suggestions.length > 0) {
2597
+ const intro = document.createElement("p");
2598
+ intro.textContent = "Did you mean:";
2599
+ const list = document.createElement("ul");
2600
+ for (const s of suggestions) {
2601
+ const li = document.createElement("li");
2602
+ const a = document.createElement("a");
2603
+ a.href = routeHref(`/${s}`);
2604
+ a.textContent = s;
2605
+ li.appendChild(a);
2606
+ list.appendChild(li);
2607
+ }
2608
+ box.append(intro, list);
2609
+ }
2610
+ dom.categoryOverview.appendChild(box);
2350
2611
  }
2351
2612
  async function navigateOverview(folderPath) {
2352
2613
  try {
2353
2614
  const res = await fetch(overviewUrl(folderPath));
2354
2615
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
2355
2616
  const overview = await res.json();
2356
- session.current = null;
2357
- session.currentVariant = null;
2358
- session.renderedHtmlCache = null;
2359
- session.rawRenderedHtml = null;
2360
- session.dataCache = null;
2361
- session.dataRaw = null;
2362
- session.stageMode = "single";
2363
- dom.variantBar.hidden = true;
2364
- dom.panel.hidden = true;
2365
- dom.singlePreview.hidden = true;
2366
- dom.categoryOverview.hidden = false;
2367
- dom.variantsGalleryEl.hidden = true;
2368
- dom.stageView.removeAttribute("data-layout");
2369
- dom.dockEl.dataset.mode = "overview";
2370
- switchDockTab("console");
2371
- setActiveTreeItem(folderPath);
2617
+ showFolderArea(folderPath);
2372
2618
  renderOverview(overview);
2373
2619
  await renderPanel();
2374
2620
  } catch (e) {
2375
2621
  console.error("[pd] Overview error:", e);
2622
+ showStageMessage(`Could not load \u201C${folderPath}\u201D: ${e instanceof Error ? e.message : String(e)}`);
2376
2623
  }
2377
2624
  }
2378
2625
  async function navigateVariants(componentId) {
@@ -2546,6 +2793,10 @@ function connectHmr() {
2546
2793
  if (link) link.href = href;
2547
2794
  });
2548
2795
  }
2796
+ void pdLinksLoaded().then(() => {
2797
+ document.querySelectorAll("#preview-frame, .gallery-item iframe").forEach(resetContentHeight);
2798
+ remeasureAll();
2799
+ });
2549
2800
  if (store.local.panelTab === "relationships" && session.current) {
2550
2801
  void fetch(componentUrl(session.current.id)).then((r) => r.ok ? r.json() : Promise.reject(r.status)).then((d) => {
2551
2802
  session.current = d;
@@ -2618,6 +2869,14 @@ function connectHmr() {
2618
2869
  }
2619
2870
  });
2620
2871
  }
2872
+ function pdLinksLoaded() {
2873
+ const links = [...document.querySelectorAll("iframe")].flatMap((f) => [...f.contentDocument?.querySelectorAll("link[data-pd-comp]") ?? []]);
2874
+ return Promise.all(links.map((link) => new Promise((resolve) => {
2875
+ link.addEventListener("load", () => resolve(), { once: true });
2876
+ link.addEventListener("error", () => resolve(), { once: true });
2877
+ setTimeout(resolve, 1e3);
2878
+ }))).then(() => void 0);
2879
+ }
2621
2880
 
2622
2881
  // src/app/router.ts
2623
2882
  function resolvePathKind(path) {
@@ -2646,6 +2905,10 @@ async function routeFromCurrentLocation() {
2646
2905
  applyCvd();
2647
2906
  }
2648
2907
  const path = routeFromPathname(window.location.pathname).slice(1);
2908
+ if (!path && window.location.hash.startsWith("#/")) {
2909
+ history.replaceState(history.state, "", routeHref(window.location.hash.slice(1)) + window.location.search);
2910
+ return routeFromCurrentLocation();
2911
+ }
2649
2912
  const variant = window.location.hash.slice(1) || void 0;
2650
2913
  if (!path) return;
2651
2914
  if (path.endsWith("/variants")) {
@@ -2662,16 +2925,10 @@ async function routeFromCurrentLocation() {
2662
2925
  } else {
2663
2926
  await navigate(path, variant);
2664
2927
  }
2665
- } else {
2928
+ } else if (kind === "group") {
2666
2929
  await navigateOverview(path);
2667
- }
2668
- }
2669
-
2670
- // src/stage/preview-sizing.ts
2671
- function setVpProp(el2, prop, value) {
2672
- const cur = parseFloat(el2.style.getPropertyValue(prop)) || 0;
2673
- if (Math.abs(value - cur) > 1) {
2674
- el2.style.setProperty(prop, `${value}px`);
2930
+ } else {
2931
+ await navigateUnknown(path, variant);
2675
2932
  }
2676
2933
  }
2677
2934
 
@@ -2683,23 +2940,20 @@ function listenForPreviewMessages() {
2683
2940
  if (data.type === "pd-height" && typeof data.height === "number") {
2684
2941
  const height = data.height;
2685
2942
  document.querySelectorAll("iframe").forEach((f) => {
2686
- if (f.contentWindow === e.source) {
2687
- const el2 = f;
2688
- if (el2.closest('[data-layout="full"]')) return;
2689
- setVpProp(el2, "--vp-h", height);
2943
+ const frame = f;
2944
+ if (frame.contentWindow !== e.source) return;
2945
+ acceptContentHeight(frame, height);
2946
+ });
2947
+ } else if (data.type === "pd-width" && typeof data.width === "number" && typeof data.at === "number") {
2948
+ const { width, at } = data;
2949
+ const scroll = typeof data.scroll === "number" ? data.scroll : at;
2950
+ document.querySelectorAll("iframe").forEach((f) => {
2951
+ const frame = f;
2952
+ if (frame.contentWindow !== e.source) return;
2953
+ if (acceptContentWidth(frame, width, at, scroll) && frame === dom.iframe) {
2954
+ requestAnimationFrame(updateReadout);
2690
2955
  }
2691
2956
  });
2692
- } else if (data.type === "pd-width" && typeof data.width === "number") {
2693
- session.lastPdWidth = data.width;
2694
- if (store.url.viewport === "content") {
2695
- const width = data.width;
2696
- document.querySelectorAll("iframe").forEach((f) => {
2697
- if (f.contentWindow === e.source) {
2698
- setVpProp(f, "--vp-w", width);
2699
- }
2700
- });
2701
- requestAnimationFrame(updateReadout);
2702
- }
2703
2957
  } else if (data.type === "pd-fp") {
2704
2958
  if (data.action === "exit") dom.fullpageDialog.close();
2705
2959
  else if (data.action === "active") showFullpageChrome();
@@ -2739,6 +2993,11 @@ function mount() {
2739
2993
  app.innerHTML = renderShellHtml();
2740
2994
  initDom(app);
2741
2995
  listenForPreviewMessages();
2996
+ initContentWidth();
2997
+ initContentHeight((frame) => {
2998
+ const id = frame.contentDocument?.body?.dataset.componentId ?? session.current?.id ?? "preview";
2999
+ renderConsole(`${id}: height depends on the preview's own height (a viewport-relative rule such as height: 100vh), so the frame keeps its last steady height and the content scrolls inside it.`);
3000
+ });
2742
3001
  dom.searchInput.addEventListener("input", () => {
2743
3002
  session.searchQuery = dom.searchInput.value;
2744
3003
  filterTree(session.searchQuery);
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
- // packages/config/src/index.ts
2
- function defineConfig(config) {
3
- return config;
4
- }
1
+ import {
2
+ defineConfig
3
+ } from "./chunks/chunk-3WZHFYHZ.js";
5
4
  export {
6
5
  defineConfig
7
6
  };