pattern-dump 0.1.0-alpha.1 → 0.1.0-alpha.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,6 +1,24 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0-alpha.1
3
+ ## 0.1.0-alpha.3
4
+
5
+ Fixes found while moving a second PatternLab styleguide (n8d) to Pattern Dump.
6
+
7
+ ### Fixed
8
+
9
+ - A numbered `templates` or `pages` folder now gets the `full` preview layout its category promises.
10
+ The category was matched with its ordering prefix still attached, so a PatternLab tree
11
+ (`04-templates`, `05-pages`) could never hit the documented default — only a per-folder
12
+ `preview: full` could. Folder *labels* still keep the number: `2-col` is a name and `04-templates`
13
+ a prefix, and nothing in the string tells them apart, so a numbered folder sets `title:` for a
14
+ clean label.
15
+ - A `requireScript` or `globalScripts` id with no matching `<scriptsDir>/<id>.ts` is reported instead
16
+ of failing inside the preview. `pd dev` names every unresolvable id at startup — with the component
17
+ for a `requireScript` — and the script's URL answers 404 saying which file it expected and where,
18
+ rather than embedding a raw `ENOENT`. Scripts are TypeScript, so a `.js` file of the same name does
19
+ not satisfy an id; that is the common case this hid.
20
+
21
+ ## 0.1.0-alpha.2
4
22
 
5
23
  Fixes found while moving a PatternLab styleguide to Pattern Dump, and the changes they needed.
6
24
 
@@ -39,6 +57,11 @@ These can change how an existing project loads or renders:
39
57
 
40
58
  ### Fixed
41
59
 
60
+ - A `centered` preview whose height comes from the viewport (`height: 200vh` in the base layer, a
61
+ percentage chain) no longer grows without bound. Content-fitting such a height is circular — the
62
+ frame's height feeds the content's — and the frame used to double every tick to the browser's
63
+ layout ceiling, showing a size like `785 × 33554428`. The run of growth is now detected, the frame
64
+ keeps its last steady height and the content scrolls inside it, and the Console says why.
42
65
  - SCSS edits reach the preview live when `styles.entry` is set, and edits to base-layer styles
43
66
  update live too.
44
67
  - `@import`, `@charset`, `@layer a, b` and `@namespace` kept their `;`: the next rule was being
package/README.md CHANGED
@@ -4,7 +4,7 @@ A workbench for HTML and CSS design systems, and a successor to PatternLab. Writ
4
4
  Handlebars templates with SCSS, data and Markdown docs; Pattern Dump shows them live, checks them, maps
5
5
  how they relate, and publishes the result as a static site.
6
6
 
7
- > **Alpha (0.1.0-alpha.1).** Published under the `alpha` tag. Options and file conventions can still change
7
+ > **Alpha (0.1.0-alpha.3).** Published under the `alpha` tag. Options and file conventions can still change
8
8
  > between releases.
9
9
 
10
10
  ## Requirements
@@ -380,9 +380,9 @@ function createStore() {
380
380
  function notify() {
381
381
  for (const fn of listeners) fn();
382
382
  }
383
- function persist(state) {
383
+ function persist(state2) {
384
384
  try {
385
- localStorage.setItem(STORAGE_KEY, toJSON(state));
385
+ localStorage.setItem(STORAGE_KEY, toJSON(state2));
386
386
  } catch {
387
387
  }
388
388
  }
@@ -1728,6 +1728,48 @@ function setVpProp(el2, prop, value) {
1728
1728
  }
1729
1729
  }
1730
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
+
1731
1773
  // src/stage/content-width.ts
1732
1774
  var measuring = /* @__PURE__ */ new WeakMap();
1733
1775
  var fitted = /* @__PURE__ */ new WeakMap();
@@ -1819,10 +1861,10 @@ function setWidthNow(frame, width) {
1819
1861
  function acceptContentWidth(frame, width, at, scroll) {
1820
1862
  const full = measuring.get(frame);
1821
1863
  if (full === void 0) {
1822
- const state = fitted.get(frame);
1823
- if (state === void 0 || scroll <= at + 1) return false;
1864
+ const state2 = fitted.get(frame);
1865
+ if (state2 === void 0 || scroll <= at + 1) return false;
1824
1866
  const parentWidth = frame.parentElement?.clientWidth ?? Infinity;
1825
- if (state === "measured" && scroll < parentWidth) {
1867
+ if (state2 === "measured" && scroll < parentWidth) {
1826
1868
  fitted.set(frame, "widened");
1827
1869
  setWidthNow(frame, scroll);
1828
1870
  } else {
@@ -2053,6 +2095,7 @@ function initViewport(onFullscreen) {
2053
2095
  iframeObserver.observe(dom.iframe);
2054
2096
  iframeObserver.observe(dom.stageView);
2055
2097
  dom.iframe.addEventListener("load", () => {
2098
+ resetContentHeight(dom.iframe);
2056
2099
  measureContentWidth(dom.iframe);
2057
2100
  updateReadout();
2058
2101
  syncFrameFocusListeners();
@@ -2326,6 +2369,7 @@ async function buildVariantsGallery() {
2326
2369
  f.addEventListener("load", () => {
2327
2370
  if (store.local.theme) applyThemeToIframe(f, store.local.theme);
2328
2371
  const h = f.contentDocument?.documentElement?.scrollHeight;
2372
+ resetContentHeight(f);
2329
2373
  if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
2330
2374
  measureContentWidth(f);
2331
2375
  });
@@ -2367,6 +2411,7 @@ function renderOverview(overview) {
2367
2411
  f.addEventListener("load", () => {
2368
2412
  if (store.local.theme) applyThemeToIframe(f, store.local.theme);
2369
2413
  const h = f.contentDocument?.documentElement?.scrollHeight;
2414
+ resetContentHeight(f);
2370
2415
  if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
2371
2416
  measureContentWidth(f);
2372
2417
  });
@@ -2748,7 +2793,10 @@ function connectHmr() {
2748
2793
  if (link) link.href = href;
2749
2794
  });
2750
2795
  }
2751
- void pdLinksLoaded().then(remeasureAll);
2796
+ void pdLinksLoaded().then(() => {
2797
+ document.querySelectorAll("#preview-frame, .gallery-item iframe").forEach(resetContentHeight);
2798
+ remeasureAll();
2799
+ });
2752
2800
  if (store.local.panelTab === "relationships" && session.current) {
2753
2801
  void fetch(componentUrl(session.current.id)).then((r) => r.ok ? r.json() : Promise.reject(r.status)).then((d) => {
2754
2802
  session.current = d;
@@ -2892,11 +2940,9 @@ function listenForPreviewMessages() {
2892
2940
  if (data.type === "pd-height" && typeof data.height === "number") {
2893
2941
  const height = data.height;
2894
2942
  document.querySelectorAll("iframe").forEach((f) => {
2895
- if (f.contentWindow === e.source) {
2896
- const el2 = f;
2897
- if (el2.closest('[data-layout="full"]')) return;
2898
- setVpProp(el2, "--vp-h", height);
2899
- }
2943
+ const frame = f;
2944
+ if (frame.contentWindow !== e.source) return;
2945
+ acceptContentHeight(frame, height);
2900
2946
  });
2901
2947
  } else if (data.type === "pd-width" && typeof data.width === "number" && typeof data.at === "number") {
2902
2948
  const { width, at } = data;
@@ -2948,6 +2994,10 @@ function mount() {
2948
2994
  initDom(app);
2949
2995
  listenForPreviewMessages();
2950
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
+ });
2951
3001
  dom.searchInput.addEventListener("input", () => {
2952
3002
  session.searchQuery = dom.searchInput.value;
2953
3003
  filterTree(session.searchQuery);
package/dist/pd.js CHANGED
@@ -30,10 +30,12 @@ function collapseComponentId(relNoExt) {
30
30
  const parent = dirname(relNoExt);
31
31
  return stem === basename(parent) ? parent : relNoExt;
32
32
  }
33
+ function stripOrderPrefixes(path) {
34
+ return path.split("/").map((seg) => seg.replace(/^\d+[-_]/, "")).join("/");
35
+ }
33
36
  function deriveHandle(id) {
34
- const parts = id.split("/");
35
- const unnumbered = (seg) => seg.replace(/^\d+[-_]/, "");
36
- return `${unnumbered(parts[0])}-${unnumbered(parts[parts.length - 1])}`;
37
+ const parts = stripOrderPrefixes(id).split("/");
38
+ return `${parts[0]}-${parts[parts.length - 1]}`;
37
39
  }
38
40
  async function buildDiscoveryMap(roots) {
39
41
  const rootList = typeof roots === "string" ? [{ dir: roots }] : roots;
@@ -1453,7 +1455,6 @@ function joinCssChunks(chunks) {
1453
1455
  }
1454
1456
  return [...charset.slice(0, 1), ...imports, ...rest].join("\n");
1455
1457
  }
1456
- var stripOrderPrefixes = (path) => path.split("/").map((seg) => seg.replace(/^\d+[-_]/, "")).join("/");
1457
1458
  var strippedIdCache = /* @__PURE__ */ new WeakMap();
1458
1459
  function strippedIds(componentIds) {
1459
1460
  let map = strippedIdCache.get(componentIds);
@@ -2358,6 +2359,99 @@ async function buildStaticSite(config, root, opts) {
2358
2359
  return report;
2359
2360
  }
2360
2361
 
2362
+ // packages/dev-server/src/requires.ts
2363
+ import { readFile as readFile8 } from "node:fs/promises";
2364
+ import { existsSync as existsSync3 } from "node:fs";
2365
+ import { basename as basename3, join as join12, relative as relative5 } from "node:path";
2366
+ function normalizeScriptId(raw) {
2367
+ let s = raw.trim().replace(/^\[(.+)\]$/, "$1").trim();
2368
+ s = basename3(s);
2369
+ s = s.replace(/\.(ts|js)$/, "");
2370
+ return s;
2371
+ }
2372
+ function parseFrontmatterList(md, key) {
2373
+ const values = [];
2374
+ const re = new RegExp(`^${key}:\\s*(.+)$`, "mg");
2375
+ for (const [, val] of md.matchAll(re)) {
2376
+ values.push(val.trim());
2377
+ }
2378
+ return values;
2379
+ }
2380
+ async function aggregateRequires(componentId, patternsDir, flatNameToPath, discoveryMap, visited = /* @__PURE__ */ new Set()) {
2381
+ if (visited.has(componentId)) return { scripts: [], assets: [] };
2382
+ visited.add(componentId);
2383
+ const entry = discoveryMap.get(componentId);
2384
+ let ownScripts = [];
2385
+ let ownAssets = [];
2386
+ if (entry?.mdPath) {
2387
+ try {
2388
+ const md = await readFile8(entry.mdPath, "utf-8");
2389
+ ownScripts = parseFrontmatterList(md, "requireScript").map(normalizeScriptId);
2390
+ ownAssets = parseFrontmatterList(md, "requireAssets").map((v) => v.trim().replace(/^\[(.+)\]$/, "$1").trim());
2391
+ } catch {
2392
+ }
2393
+ }
2394
+ const allScripts = [...ownScripts];
2395
+ const allAssets = [...ownAssets];
2396
+ const hbsPath = entry?.hbsPath ?? "";
2397
+ let hbs = "";
2398
+ try {
2399
+ hbs = hbsPath ? await readFile8(hbsPath, "utf-8") : "";
2400
+ } catch {
2401
+ }
2402
+ for (const flatName of parsePartialsFromHbs(hbs)) {
2403
+ const childPath = flatNameToPath.get(flatName);
2404
+ if (childPath) {
2405
+ const child = await aggregateRequires(childPath, patternsDir, flatNameToPath, discoveryMap, visited);
2406
+ allScripts.push(...child.scripts);
2407
+ allAssets.push(...child.assets);
2408
+ }
2409
+ }
2410
+ return {
2411
+ scripts: [...new Set(allScripts)],
2412
+ assets: [...new Set(allAssets)]
2413
+ };
2414
+ }
2415
+ async function loadSvgAssets(ids, assetsDir) {
2416
+ const contents = [];
2417
+ for (const id of ids) {
2418
+ for (const candidate of [`${id}.svg`, `images/${id}.svg`]) {
2419
+ try {
2420
+ const raw = await readFile8(join12(assetsDir, candidate), "utf-8");
2421
+ contents.push(hideSvg(raw));
2422
+ break;
2423
+ } catch {
2424
+ }
2425
+ }
2426
+ }
2427
+ return contents;
2428
+ }
2429
+ function hideSvg(svg) {
2430
+ return svg.replace(/<svg(\s|>)/, '<svg$1style="display:none;position:absolute" aria-hidden="true" ');
2431
+ }
2432
+ async function reportUnresolvableScripts(discoveryMap, scriptsDirs, globalScripts, cwd) {
2433
+ const resolves = (id) => scriptsDirs.some((d) => existsSync3(join12(d, `${id}.ts`)));
2434
+ const where = scriptsDirs.length ? scriptsDirs.map((d) => relative5(cwd, d) || d).join(" or ") : "no scriptsDir configured";
2435
+ for (const id of globalScripts) {
2436
+ if (id.includes("://") || resolves(normalizeScriptId(id))) continue;
2437
+ console.warn(`[dev-server] globalScripts "${id}": no ${normalizeScriptId(id)}.ts in ${where}`);
2438
+ }
2439
+ for (const [componentId, entry] of discoveryMap) {
2440
+ if (!entry.mdPath) continue;
2441
+ let md;
2442
+ try {
2443
+ md = await readFile8(entry.mdPath, "utf-8");
2444
+ } catch {
2445
+ continue;
2446
+ }
2447
+ for (const raw of parseFrontmatterList(md, "requireScript")) {
2448
+ const id = normalizeScriptId(raw);
2449
+ if (resolves(id)) continue;
2450
+ console.warn(`[dev-server] requireScript "${raw}" on ${componentId}: no ${id}.ts in ${where}`);
2451
+ }
2452
+ }
2453
+ }
2454
+
2361
2455
  // packages/dev-server/src/server-state.ts
2362
2456
  function createServerState() {
2363
2457
  return {
@@ -2399,25 +2493,25 @@ function applyStyleData(state, data) {
2399
2493
  }
2400
2494
 
2401
2495
  // packages/dev-server/src/routes.ts
2402
- import { basename as basename4, extname as extname2, join as join18 } from "node:path";
2496
+ import { basename as basename4, extname as extname2, join as join18, relative as relative6 } from "node:path";
2403
2497
  import { readFile as readFile15, stat as stat4 } from "node:fs/promises";
2404
- import { existsSync as existsSync4 } from "node:fs";
2498
+ import { existsSync as existsSync5 } from "node:fs";
2405
2499
 
2406
2500
  // packages/dev-server/src/component-tree.ts
2501
+ import { readFile as readFile11 } from "node:fs/promises";
2502
+ import { join as join15 } from "node:path";
2503
+
2504
+ // packages/dev-server/src/layout-mode.ts
2407
2505
  import { readFile as readFile10 } from "node:fs/promises";
2408
2506
  import { join as join14 } from "node:path";
2409
2507
 
2410
- // packages/dev-server/src/layout-mode.ts
2508
+ // packages/dev-server/src/pattern-dirs.ts
2411
2509
  import { readFile as readFile9 } from "node:fs/promises";
2412
2510
  import { join as join13 } from "node:path";
2413
-
2414
- // packages/dev-server/src/pattern-dirs.ts
2415
- import { readFile as readFile8 } from "node:fs/promises";
2416
- import { join as join12 } from "node:path";
2417
2511
  async function readFolderFile(dirs, rel) {
2418
2512
  for (const dir of typeof dirs === "string" ? [dirs] : dirs) {
2419
2513
  try {
2420
- return await readFile8(join12(dir, rel), "utf-8");
2514
+ return await readFile9(join13(dir, rel), "utf-8");
2421
2515
  } catch {
2422
2516
  }
2423
2517
  }
@@ -2429,8 +2523,8 @@ var FULL_CATEGORIES = /* @__PURE__ */ new Set(["pages", "templates"]);
2429
2523
  async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
2430
2524
  const parts = componentId.split("/");
2431
2525
  const category = parts[0];
2432
- let layout = FULL_CATEGORIES.has(category) ? "full" : "centered";
2433
- const categoryIndex = await readFolderFile(patternsDir, join13(category, "index.md"));
2526
+ let layout = FULL_CATEGORIES.has(stripOrderPrefixes(category)) ? "full" : "centered";
2527
+ const categoryIndex = await readFolderFile(patternsDir, join14(category, "index.md"));
2434
2528
  if (categoryIndex !== null) {
2435
2529
  const m = categoryIndex.match(/^preview:\s*(centered|full)$/m);
2436
2530
  if (m) layout = m[1];
@@ -2438,7 +2532,7 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
2438
2532
  const mdPath = discoveryMap?.get(componentId)?.mdPath;
2439
2533
  if (mdPath) {
2440
2534
  try {
2441
- const md = await readFile9(mdPath, "utf-8");
2535
+ const md = await readFile10(mdPath, "utf-8");
2442
2536
  const m = md.match(/^preview:\s*(centered|full)$/m);
2443
2537
  if (m) layout = m[1];
2444
2538
  } catch {
@@ -2531,7 +2625,7 @@ function folderTitle(segment) {
2531
2625
  const text = words.join(" ");
2532
2626
  return text.charAt(0).toUpperCase() + text.slice(1);
2533
2627
  }
2534
- var readText = (path) => readFile10(path, "utf-8").catch(() => null);
2628
+ var readText = (path) => readFile11(path, "utf-8").catch(() => null);
2535
2629
  function orderOf(md) {
2536
2630
  return md === null ? void 0 : parseOrder(readFrontmatter(md)["order"]);
2537
2631
  }
@@ -2582,7 +2676,7 @@ async function buildTreeNodeFromMap(nodePath, idsUnderPath, patternsDir, overvie
2582
2676
  if (o && "weight" in o) weight = o.weight;
2583
2677
  }
2584
2678
  }
2585
- const indexMd = await readFolderFile(patternsDir, join14(nodePath, "index.md"));
2679
+ const indexMd = await readFolderFile(patternsDir, join15(nodePath, "index.md"));
2586
2680
  const folderOrder = orderOf(indexMd);
2587
2681
  const built = [];
2588
2682
  for (const childPath of directChildPaths(nodePath, idsUnderPath)) {
@@ -2630,7 +2724,7 @@ async function buildTreeNodeFromMap(nodePath, idsUnderPath, patternsDir, overvie
2630
2724
  import { readFile as readFile14 } from "node:fs/promises";
2631
2725
 
2632
2726
  // packages/dev-server/src/markdown.ts
2633
- import { readFile as readFile11 } from "node:fs/promises";
2727
+ import { readFile as readFile12 } from "node:fs/promises";
2634
2728
  import MarkdownIt from "markdown-it";
2635
2729
  var _md = new MarkdownIt({ html: false, linkify: false });
2636
2730
  _md.validateLink = (url) => {
@@ -2719,7 +2813,7 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2719
2813
  return;
2720
2814
  }
2721
2815
  try {
2722
- const src = await readFile11(entry.hbsPath, "utf-8");
2816
+ const src = await readFile12(entry.hbsPath, "utf-8");
2723
2817
  out.set(idx, wrapWithCopyButton(highlight(src, "handlebars"), src));
2724
2818
  } catch (e) {
2725
2819
  console.warn(`[dev-server] src-template read error [${spec}]:`, e);
@@ -2744,8 +2838,8 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2744
2838
  }
2745
2839
  const baseJsonPath = discoveryMap.get(compId)?.jsonPath;
2746
2840
  const variantJsonPath = variantEntry.jsonPath;
2747
- const baseData = baseJsonPath ? JSON.parse(await readFile11(baseJsonPath, "utf-8")) : {};
2748
- const variantData = variantJsonPath ? JSON.parse(await readFile11(variantJsonPath, "utf-8")) : {};
2841
+ const baseData = baseJsonPath ? JSON.parse(await readFile12(baseJsonPath, "utf-8")) : {};
2842
+ const variantData = variantJsonPath ? JSON.parse(await readFile12(variantJsonPath, "utf-8")) : {};
2749
2843
  const merged = deepMerge(structuredClone(baseData), variantData);
2750
2844
  const src = JSON.stringify(merged, null, 2);
2751
2845
  out.set(idx, wrapWithCopyButton(highlight(src, "json"), src));
@@ -2755,7 +2849,7 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2755
2849
  out.set(idx, `<p class="doc-error">[src-data: ${escapeHtml(spec)} \u2014 no data file]</p>`);
2756
2850
  return;
2757
2851
  }
2758
- const src = await readFile11(jsonPath, "utf-8");
2852
+ const src = await readFile12(jsonPath, "utf-8");
2759
2853
  out.set(idx, wrapWithCopyButton(highlight(src, "json"), src));
2760
2854
  }
2761
2855
  } catch (e) {
@@ -2785,7 +2879,7 @@ async function _resolveFence(info, content, idx, renderComponent, out, discovery
2785
2879
  }
2786
2880
 
2787
2881
  // packages/dev-server/src/css-closure.ts
2788
- import { readFile as readFile12 } from "node:fs/promises";
2882
+ import { readFile as readFile13 } from "node:fs/promises";
2789
2883
  import { dirname as dirname5, resolve as resolve7 } from "node:path";
2790
2884
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2791
2885
  import postcss4 from "postcss";
@@ -2825,7 +2919,7 @@ async function computeCssClosure(componentId, compReg, patternsDir, classOwnerIn
2825
2919
  const hbsPath = discoveryMap.get(id)?.hbsPath ?? "";
2826
2920
  let hbs = "";
2827
2921
  try {
2828
- hbs = hbsPath ? await readFile12(hbsPath, "utf-8") : "";
2922
+ hbs = hbsPath ? await readFile13(hbsPath, "utf-8") : "";
2829
2923
  } catch {
2830
2924
  }
2831
2925
  const { styleUses } = deriveClassBorrowingFields(hbs, id, classOwnerIndex);
@@ -2881,76 +2975,6 @@ function linkableClosure(closureIds, perComponentCss) {
2881
2975
  return closureIds.filter((id) => !!perComponentCss.get(id));
2882
2976
  }
2883
2977
 
2884
- // packages/dev-server/src/requires.ts
2885
- import { readFile as readFile13 } from "node:fs/promises";
2886
- import { join as join15, basename as basename3 } from "node:path";
2887
- function normalizeScriptId(raw) {
2888
- let s = raw.trim().replace(/^\[(.+)\]$/, "$1").trim();
2889
- s = basename3(s);
2890
- s = s.replace(/\.(ts|js)$/, "");
2891
- return s;
2892
- }
2893
- function parseFrontmatterList(md, key) {
2894
- const values = [];
2895
- const re = new RegExp(`^${key}:\\s*(.+)$`, "mg");
2896
- for (const [, val] of md.matchAll(re)) {
2897
- values.push(val.trim());
2898
- }
2899
- return values;
2900
- }
2901
- async function aggregateRequires(componentId, patternsDir, flatNameToPath, discoveryMap, visited = /* @__PURE__ */ new Set()) {
2902
- if (visited.has(componentId)) return { scripts: [], assets: [] };
2903
- visited.add(componentId);
2904
- const entry = discoveryMap.get(componentId);
2905
- let ownScripts = [];
2906
- let ownAssets = [];
2907
- if (entry?.mdPath) {
2908
- try {
2909
- const md = await readFile13(entry.mdPath, "utf-8");
2910
- ownScripts = parseFrontmatterList(md, "requireScript").map(normalizeScriptId);
2911
- ownAssets = parseFrontmatterList(md, "requireAssets").map((v) => v.trim().replace(/^\[(.+)\]$/, "$1").trim());
2912
- } catch {
2913
- }
2914
- }
2915
- const allScripts = [...ownScripts];
2916
- const allAssets = [...ownAssets];
2917
- const hbsPath = entry?.hbsPath ?? "";
2918
- let hbs = "";
2919
- try {
2920
- hbs = hbsPath ? await readFile13(hbsPath, "utf-8") : "";
2921
- } catch {
2922
- }
2923
- for (const flatName of parsePartialsFromHbs(hbs)) {
2924
- const childPath = flatNameToPath.get(flatName);
2925
- if (childPath) {
2926
- const child = await aggregateRequires(childPath, patternsDir, flatNameToPath, discoveryMap, visited);
2927
- allScripts.push(...child.scripts);
2928
- allAssets.push(...child.assets);
2929
- }
2930
- }
2931
- return {
2932
- scripts: [...new Set(allScripts)],
2933
- assets: [...new Set(allAssets)]
2934
- };
2935
- }
2936
- async function loadSvgAssets(ids, assetsDir) {
2937
- const contents = [];
2938
- for (const id of ids) {
2939
- for (const candidate of [`${id}.svg`, `images/${id}.svg`]) {
2940
- try {
2941
- const raw = await readFile13(join15(assetsDir, candidate), "utf-8");
2942
- contents.push(hideSvg(raw));
2943
- break;
2944
- } catch {
2945
- }
2946
- }
2947
- }
2948
- return contents;
2949
- }
2950
- function hideSvg(svg) {
2951
- return svg.replace(/<svg(\s|>)/, '<svg$1style="display:none;position:absolute" aria-hidden="true" ');
2952
- }
2953
-
2954
2978
  // packages/dev-server/src/detail.ts
2955
2979
  async function buildComponentDetail(componentId, patternsDir, renderFn, tracker, defaultTheme, flatNameToPath, compositionReg, scssGraph, classOwnerIndex, discoveryMap, baseProvenance) {
2956
2980
  const parts = componentId.split("/");
@@ -3075,7 +3099,7 @@ async function buildComponentDetail(componentId, patternsDir, renderFn, tracker,
3075
3099
  import { join as join16 } from "node:path";
3076
3100
  async function buildFolderOverview(folderPath, patternsDir, renderFn, discoveryMap) {
3077
3101
  const category = folderPath.split("/")[0];
3078
- let folderLayout = FULL_CATEGORIES.has(category) ? "full" : "centered";
3102
+ let folderLayout = FULL_CATEGORIES.has(stripOrderPrefixes(category)) ? "full" : "centered";
3079
3103
  let doc;
3080
3104
  try {
3081
3105
  const rawIndex = await readFolderFile(patternsDir, join16(folderPath, "index.md"));
@@ -3214,10 +3238,10 @@ function normalizeAssetRefs(text, rootNames) {
3214
3238
  }
3215
3239
 
3216
3240
  // packages/dev-server/src/assets.ts
3217
- import { existsSync as existsSync3 } from "node:fs";
3241
+ import { existsSync as existsSync4 } from "node:fs";
3218
3242
  import { createRequire as createRequire2 } from "node:module";
3219
3243
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3220
- function resolveDevUiDist(moduleUrl, exists = existsSync3) {
3244
+ function resolveDevUiDist(moduleUrl, exists = existsSync4) {
3221
3245
  const packaged = fileURLToPath4(new URL("./dev-ui/", moduleUrl));
3222
3246
  if (exists(`${packaged}main.js`)) return packaged;
3223
3247
  return fileURLToPath4(new URL("../../../apps/dev-ui/dist/", moduleUrl));
@@ -3310,7 +3334,12 @@ async function handleRequest(req, res, ctx) {
3310
3334
  const scriptId = normalizeScriptId(decodeURIComponent(basename4(pathname, ".js")));
3311
3335
  try {
3312
3336
  if (!state.scriptCache.has(scriptId)) {
3313
- const entryPoint = scriptsDirs.map((d) => join18(d, `${scriptId}.ts`)).find((f) => existsSync4(f)) ?? join18(scriptsDirs[0], `${scriptId}.ts`);
3337
+ const entryPoint = scriptsDirs.map((d) => join18(d, `${scriptId}.ts`)).find((f) => existsSync5(f));
3338
+ if (entryPoint === void 0) {
3339
+ res.writeHead(404, { "Content-Type": "text/plain" });
3340
+ res.end(`No script "${scriptId}": scripts are TypeScript, so ${scriptId}.ts is expected in ` + scriptsDirs.map((d) => relative6(cwd, d) || d).join(" or "));
3341
+ return;
3342
+ }
3314
3343
  const js = await compileScriptFile(entryPoint);
3315
3344
  state.scriptCache.set(scriptId, js);
3316
3345
  }
@@ -3594,7 +3623,7 @@ function describeBindError(e, port2, role, ports) {
3594
3623
  }
3595
3624
 
3596
3625
  // packages/dev-server/src/watch.ts
3597
- import { basename as basename5, extname as extname3, relative as relative5, resolve as resolve8 } from "node:path";
3626
+ import { basename as basename5, extname as extname3, relative as relative7, resolve as resolve8 } from "node:path";
3598
3627
  import chokidar from "chokidar";
3599
3628
  function startWatching(ctx) {
3600
3629
  const { cfg, state, render, tracker, hotreloader } = ctx;
@@ -3706,7 +3735,7 @@ function startWatching(ctx) {
3706
3735
  }
3707
3736
  return void 0;
3708
3737
  };
3709
- const rel = relative5(patternsDir, filePath);
3738
+ const rel = relative7(patternsDir, filePath);
3710
3739
  if (event === "change") state.discoveryResult ??= await buildDiscoveryMap(patternRoots);
3711
3740
  let owner = ownerIn(state.discoveryResult);
3712
3741
  if (event === "change") {
@@ -3788,21 +3817,21 @@ function startWatching(ctx) {
3788
3817
  if (ext === ".scss" && !(themesDir && filePath.startsWith(themesDir))) {
3789
3818
  await handleComponentScss(filePath);
3790
3819
  } else if (ext === ".hbs" && filePath.startsWith(patternsDir)) {
3791
- await rebuildAndSignalTree(`Pattern added: ${relative5(patternsDir, filePath)}`);
3820
+ await rebuildAndSignalTree(`Pattern added: ${relative7(patternsDir, filePath)}`);
3792
3821
  } else if (ext === ".md" && filePath.startsWith(patternsDir)) {
3793
3822
  await handleMarkdown(filePath, "add");
3794
3823
  }
3795
3824
  });
3796
3825
  watcher.on("unlink", async (filePath) => {
3797
3826
  if (extname3(filePath) === ".hbs" && filePath.startsWith(patternsDir)) {
3798
- await rebuildAndSignalTree(`Pattern removed: ${relative5(patternsDir, filePath)}`);
3827
+ await rebuildAndSignalTree(`Pattern removed: ${relative7(patternsDir, filePath)}`);
3799
3828
  } else if (extname3(filePath) === ".md" && filePath.startsWith(patternsDir)) {
3800
3829
  await handleMarkdown(filePath, "unlink");
3801
3830
  }
3802
3831
  });
3803
3832
  watcher.on("unlinkDir", async (dirPath) => {
3804
3833
  if (dirPath.startsWith(patternsDir) && dirPath !== patternsDir) {
3805
- await rebuildAndSignalTree(`Pattern dir removed: ${relative5(patternsDir, dirPath)}`);
3834
+ await rebuildAndSignalTree(`Pattern dir removed: ${relative7(patternsDir, dirPath)}`);
3806
3835
  }
3807
3836
  });
3808
3837
  return watcher;
@@ -3810,7 +3839,7 @@ function startWatching(ctx) {
3810
3839
 
3811
3840
  // packages/dev-server/src/export-io.ts
3812
3841
  import { readFile as readFile16, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
3813
- import { dirname as dirname6, join as join19, relative as relative6, resolve as resolve9 } from "node:path";
3842
+ import { dirname as dirname6, join as join19, relative as relative8, resolve as resolve9 } from "node:path";
3814
3843
 
3815
3844
  // packages/dev-server/src/export.ts
3816
3845
  var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3960,7 +3989,7 @@ async function planExport(config, cwd) {
3960
3989
  const hbs = await readFile16(entry.hbsPath, "utf-8");
3961
3990
  inputs.push({ id, hbs });
3962
3991
  hbsById.set(id, hbs);
3963
- relPathById.set(id, relative6(patternsDir, entry.hbsPath).replace(/\.hbs$/, ""));
3992
+ relPathById.set(id, relative8(patternsDir, entry.hbsPath).replace(/\.hbs$/, ""));
3964
3993
  if (entry.jsonPath) {
3965
3994
  try {
3966
3995
  dataJsonById.set(id, await readFile16(entry.jsonPath, "utf-8"));
@@ -4047,7 +4076,7 @@ async function writeThreadedIncluders(threaded, patternsDir, sink) {
4047
4076
  const res = rewriteIncludes(src, map);
4048
4077
  if (res.rewrites === 0) continue;
4049
4078
  rewrites += res.rewrites;
4050
- const rel = relative6(patternsDir, hbsPath);
4079
+ const rel = relative8(patternsDir, hbsPath);
4051
4080
  const outPath = join19(sink, rel);
4052
4081
  await mkdir4(dirname6(outPath), { recursive: true });
4053
4082
  await writeFile4(outPath, res.hbs, "utf-8");
@@ -4231,6 +4260,7 @@ async function startDevServer(config, cwd, opts = {}) {
4231
4260
  const styleData = await buildAllStyleData(styleRoots, allComponentIds);
4232
4261
  applyStyleData(state, styleData);
4233
4262
  surfaceDocumentBase(styleRoots, styleData.baseOutsideList, cwd);
4263
+ await reportUnresolvableScripts(dr.discoveryMap, cfg.scriptsDirs, cfg.globalScripts, cwd);
4234
4264
  }
4235
4265
  const boot = {
4236
4266
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4271,9 +4301,9 @@ async function startDevServer(config, cwd, opts = {}) {
4271
4301
 
4272
4302
  // packages/core/src/index.ts
4273
4303
  import { build as esbuild } from "esbuild";
4274
- import { existsSync as existsSync5 } from "node:fs";
4304
+ import { existsSync as existsSync6 } from "node:fs";
4275
4305
  import { readFile as readFile17, writeFile as writeFile5, unlink, mkdir as mkdir5 } from "node:fs/promises";
4276
- import { join as join21, dirname as dirname7, resolve as resolve10, relative as relative7, isAbsolute } from "node:path";
4306
+ import { join as join21, dirname as dirname7, resolve as resolve10, relative as relative9, isAbsolute } from "node:path";
4277
4307
  import { tmpdir as tmpdir2 } from "node:os";
4278
4308
  import { createHash as createHash3 } from "node:crypto";
4279
4309
  import { pathToFileURL as pathToFileURL2 } from "node:url";
@@ -4316,7 +4346,7 @@ function defaultExportDir(root) {
4316
4346
  return join21(tmpdir2(), "pattern-dump", key, "export");
4317
4347
  }
4318
4348
  function assertOutsideRoot(outDir, root) {
4319
- const rel = relative7(resolve10(root), resolve10(outDir));
4349
+ const rel = relative9(resolve10(root), resolve10(outDir));
4320
4350
  const inside = rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
4321
4351
  if (inside) {
4322
4352
  throw new Error(
@@ -4387,14 +4417,14 @@ async function runExport(root, configPath, outDir) {
4387
4417
  }
4388
4418
  async function loadConfig(root, configPath) {
4389
4419
  if (configPath) {
4390
- if (!existsSync5(configPath)) {
4420
+ if (!existsSync6(configPath)) {
4391
4421
  throw new ConfigError(`--config file not found: ${configPath}`);
4392
4422
  }
4393
4423
  return validateConfig(await readConfigFile(configPath), configPath);
4394
4424
  }
4395
4425
  for (const name of ["pattern-dump.config.ts", "pattern-dump.config.json"]) {
4396
4426
  const path = join21(root, name);
4397
- if (existsSync5(path)) return validateConfig(await readConfigFile(path), name);
4427
+ if (existsSync6(path)) return validateConfig(await readConfigFile(path), name);
4398
4428
  }
4399
4429
  throw new ConfigError(
4400
4430
  `No pattern-dump.config.ts or pattern-dump.config.json found in ${root} (and no --config given)`
@@ -4609,14 +4639,14 @@ var USAGE = `Usage: pd <command> [options]
4609
4639
  help [command] Show this help`;
4610
4640
 
4611
4641
  // packages/cli/src/init/run.ts
4612
- import { existsSync as existsSync6 } from "node:fs";
4642
+ import { existsSync as existsSync7 } from "node:fs";
4613
4643
  import { readFile as readFile19 } from "node:fs/promises";
4614
- import { basename as basename6, dirname as dirname9, join as join23, relative as relative9, resolve as resolve12 } from "node:path";
4644
+ import { basename as basename6, dirname as dirname9, join as join23, relative as relative11, resolve as resolve12 } from "node:path";
4615
4645
 
4616
4646
  // packages/cli/package.json
4617
4647
  var package_default = {
4618
4648
  name: "@pattern-dump/cli",
4619
- version: "0.1.0-alpha.1",
4649
+ version: "0.1.0-alpha.3",
4620
4650
  private: true,
4621
4651
  description: "The pd command and the published pattern-dump package.",
4622
4652
  type: "module",
@@ -4652,14 +4682,14 @@ var PD_VERSION = package_default.version;
4652
4682
 
4653
4683
  // packages/cli/src/init/files.ts
4654
4684
  import { mkdir as mkdir6, readdir as readdir5, readFile as readFile18, writeFile as writeFile6 } from "node:fs/promises";
4655
- import { dirname as dirname8, join as join22, relative as relative8, sep as sep2 } from "node:path";
4685
+ import { dirname as dirname8, join as join22, relative as relative10, sep as sep2 } from "node:path";
4656
4686
  async function readTemplateDir(dir) {
4657
4687
  const out = [];
4658
4688
  async function walk(current) {
4659
4689
  for (const entry of await readdir5(current, { withFileTypes: true })) {
4660
4690
  const full = join22(current, entry.name);
4661
4691
  if (entry.isDirectory()) await walk(full);
4662
- else out.push({ path: relative8(dir, full).split(sep2).join("/"), content: await readFile18(full, "utf8") });
4692
+ else out.push({ path: relative10(dir, full).split(sep2).join("/"), content: await readFile18(full, "utf8") });
4663
4693
  }
4664
4694
  }
4665
4695
  await walk(dir);
@@ -4759,12 +4789,12 @@ function mergePackageJson(current, answers, packageName) {
4759
4789
  function dependencySpec(pdSource, cwd) {
4760
4790
  if (pdSource === void 0) return `^${PD_VERSION}`;
4761
4791
  const asPath = resolve12(cwd, pdSource);
4762
- if (existsSync6(asPath)) return `file:${asPath}`;
4792
+ if (existsSync7(asPath)) return `file:${asPath}`;
4763
4793
  return pdSource.startsWith(`${PD_PACKAGE_NAME}@`) ? pdSource.slice(PD_PACKAGE_NAME.length + 1) : pdSource;
4764
4794
  }
4765
4795
  async function insideGitRepo(target, io) {
4766
4796
  let dir = target;
4767
- while (!existsSync6(dir) && dirname9(dir) !== dir) dir = dirname9(dir);
4797
+ while (!existsSync7(dir) && dirname9(dir) !== dir) dir = dirname9(dir);
4768
4798
  return await io.run("git", ["rev-parse", "--is-inside-work-tree"], dir) === 0;
4769
4799
  }
4770
4800
  async function runInit(cmd, io) {
@@ -4789,7 +4819,7 @@ async function runInit(cmd, io) {
4789
4819
  }
4790
4820
  const pkgPath = join23(target, "package.json");
4791
4821
  let packageJson = null;
4792
- if (existsSync6(pkgPath)) {
4822
+ if (existsSync7(pkgPath)) {
4793
4823
  try {
4794
4824
  packageJson = JSON.parse(await readFile19(pkgPath, "utf8"));
4795
4825
  } catch (e) {
@@ -4807,14 +4837,14 @@ async function runInit(cmd, io) {
4807
4837
  const install = cmd.install ?? (prompt ? await io.confirm("Run npm install", true) : true);
4808
4838
  const answers = { name, brand, examples, dependency };
4809
4839
  const templates = await readTemplates(io.templatesDir);
4810
- const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync6(join23(target, p)) }, PD_PACKAGE_NAME);
4840
+ const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync7(join23(target, p)) }, PD_PACKAGE_NAME);
4811
4841
  if (!result.ok) {
4812
4842
  io.log(`\u2717 ${result.reason}`);
4813
4843
  return 1;
4814
4844
  }
4815
4845
  const { plan } = result;
4816
4846
  await applyPlan(target, plan);
4817
- const where = relative9(io.cwd, target) || ".";
4847
+ const where = relative11(io.cwd, target) || ".";
4818
4848
  io.log(`\u2713 ${plan.writes.length} files written to ${where}`);
4819
4849
  io.log(`\u2713 package.json ${plan.packageJson.created ? "created" : "updated"} \u2014 ${PD_PACKAGE_NAME} ${answers.dependency}`);
4820
4850
  for (const note of plan.packageJson.notes) io.log(` \xB7 ${note}`);
@@ -4847,7 +4877,7 @@ async function runInit(cmd, io) {
4847
4877
  }
4848
4878
 
4849
4879
  // packages/cli/src/init/local-build.ts
4850
- import { existsSync as existsSync7, statSync } from "node:fs";
4880
+ import { existsSync as existsSync8, statSync } from "node:fs";
4851
4881
  import { join as join24 } from "node:path";
4852
4882
  import { fileURLToPath as fileURLToPath6 } from "node:url";
4853
4883
  var BUILT = [
@@ -4866,9 +4896,9 @@ var mtime = (path) => {
4866
4896
  };
4867
4897
  function detectLocalBuild(moduleUrl, packageName, version) {
4868
4898
  const repo = fileURLToPath6(new URL("../../../", moduleUrl)).replace(/[\\/]$/, "");
4869
- if (!existsSync7(join24(repo, "packages/cli/src/distribution.ts"))) return null;
4899
+ if (!existsSync8(join24(repo, "packages/cli/src/distribution.ts"))) return null;
4870
4900
  const tarball = join24(repo, ".pd-pack", `${packageName}-${version}.tgz`);
4871
- if (!existsSync7(tarball)) return { repo, tarball: null, stale: false };
4901
+ if (!existsSync8(tarball)) return { repo, tarball: null, stale: false };
4872
4902
  const newestBuild = Math.max(...BUILT.map((p) => mtime(join24(repo, p))));
4873
4903
  return { repo, tarball, stale: newestBuild > mtime(tarball) };
4874
4904
  }
@@ -8,7 +8,8 @@ export type ComponentStatus = "draft" | "stable" | "deprecated";
8
8
  * in both — the markup directly in <body>, no wrapper — only the frame around it differs:
9
9
  * - "centered": the frame is sized to what the content needs (pd-width, measured at the full width
10
10
  * available) and to its height (pd-height), and the stage centres it (atoms/molecules/organisms)
11
- * - "full": the frame fills the stage width AND height, top-aligned (pages/templates)
11
+ * - "full": the frame fills the stage width AND height, top-aligned (pages/templates, with or
12
+ * without an ordering prefix: `05-pages` counts)
12
13
  * This is the RESOLVED mode (category default ← category index.md frontmatter ← component override),
13
14
  * so the shell renders directly without re-deriving precedence.
14
15
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pattern-dump",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "A workbench for HTML and CSS design systems: live component previews, docs, checks and static publishing.",
5
5
  "keywords": [
6
6
  "design-system",