pattern-dump 0.1.0-alpha.4 → 0.3.0

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 (44) hide show
  1. package/CHANGELOG.md +106 -1
  2. package/LICENSE +16 -3
  3. package/README.md +7 -7
  4. package/THIRD-PARTY-NOTICES.md +130 -0
  5. package/dist/chunks/chunk-5WFBNPXB.js +294 -0
  6. package/dist/dev-ui/main.js +215 -77
  7. package/dist/index.js +1 -1
  8. package/dist/pd.js +5833 -881
  9. package/dist/types/config/index.d.ts +93 -2
  10. package/dist/types/config/validate.d.ts +19 -1
  11. package/dist/types/contracts/dev-protocol.d.ts +106 -4
  12. package/dist/types/contracts/iframe-fit.d.ts +33 -0
  13. package/dist/types/contracts/index.d.ts +36 -0
  14. package/package.json +9 -3
  15. package/templates/base/README.md +1 -0
  16. package/templates/base/_gitignore +1 -0
  17. package/templates/base/pattern-dump.config.ts +1 -0
  18. package/templates/base/src/styles/base/document.scss +5 -1
  19. package/templates/storefront/.github/workflows/pages.yml +53 -0
  20. package/templates/storefront/assets/icon.svg +2 -0
  21. package/templates/storefront/pages/about.md +15 -0
  22. package/templates/storefront/pages/index.md +24 -0
  23. package/templates/storefront/src/patterns/components/note/note.hbs +4 -0
  24. package/templates/storefront/src/patterns/components/note/note.json +4 -0
  25. package/templates/storefront/src/patterns/components/note/note.md +11 -0
  26. package/templates/storefront/src/patterns/components/note/note.schema.json +13 -0
  27. package/templates/storefront/src/patterns/components/note/note~warning.json +4 -0
  28. package/templates/storefront/src/patterns/layouts/page/page.hbs +8 -0
  29. package/templates/storefront/src/patterns/layouts/page/page.json +8 -0
  30. package/templates/storefront/src/patterns/layouts/page/page.md +6 -0
  31. package/templates/storefront/src/patterns/layouts/page/page.schema.json +19 -0
  32. package/templates/storefront/src/patterns/partials/base/base.hbs +17 -0
  33. package/templates/storefront/src/patterns/partials/base/base.json +4 -0
  34. package/templates/storefront/src/patterns/partials/base/base.md +6 -0
  35. package/templates/storefront/src/patterns/partials/base/base.schema.json +18 -0
  36. package/templates/storefront/src/patterns/partials/head/head.hbs +1 -0
  37. package/templates/storefront/src/patterns/partials/head/head.md +6 -0
  38. package/templates/storefront/src/styles/components/note.scss +8 -0
  39. package/templates/storefront/src/styles/layouts/page.scss +6 -0
  40. package/templates/storefront/src/styles/partials/base.scss +13 -0
  41. package/templates/storefront-fills/config.txt +14 -0
  42. package/templates/storefront-fills/gitignore.txt +2 -0
  43. package/templates/storefront-fills/readme.md +60 -0
  44. package/dist/chunks/chunk-NXGPZL5L.js +0 -160
@@ -1,6 +1,6 @@
1
1
  import type { ShellBrandConfig, SwatchSlots, ThemeSwitch } from "../contracts/index.js";
2
2
  export type { ShellBrandConfig, SwatchSlots, ThemeSwitch };
3
- export { ConfigError, configProblems, validateConfig } from "./validate.js";
3
+ export { ConfigError, configProblems, sameHostWorkbenchPath, suggest, validateConfig, withSourceDefaults } from "./validate.js";
4
4
  export interface AssetsConfig {
5
5
  /** Where assets live. Only 'central' (the default: one assets dir, served at /assets/) is supported
6
6
  * so far; 'component-relative' is reserved, and the config check rejects it as not supported yet. */
@@ -156,5 +156,96 @@ export interface PatternDumpConfig {
156
156
  basePath?: string;
157
157
  audit?: boolean;
158
158
  };
159
+ /** How responsive image variants are encoded (WebP). Defaults: quality 75, effort 4. A site can override
160
+ * either in `storefront.images`. */
161
+ images?: ImageSettings;
162
+ /** The storefront: a website of real HTML pages, written from markdown, beside the workbench. */
163
+ storefront?: StorefrontConfig;
159
164
  }
160
- export declare function defineConfig(config: PatternDumpConfig): PatternDumpConfig;
165
+ /**
166
+ * The storefront. `pd dev` serves it on its own host (`live.localhost` unless `host` says otherwise),
167
+ * the workbench on `pd.localhost`, both from `/`. `pd build` writes it to its own folder, apart from
168
+ * the workbench snapshot. A page renders with the layout `layouts/<layout>` from the project's patterns.
169
+ */
170
+ /** How responsive image variants are encoded. */
171
+ export interface ImageSettings {
172
+ /** WebP quality, a whole number from 1 to 100. Default: 75. */
173
+ quality?: number;
174
+ /** WebP effort, 0 (fastest) to 6 (smallest files). Default: 4. */
175
+ effort?: number;
176
+ }
177
+ export interface StorefrontConfig {
178
+ /** Folder of the pages, relative to the root. `index.md` is `/`, `guides/setup.md` is `/guides/setup/`.
179
+ * `pagesDir`, `patternsDir` and `outDir` are three separate folders: none is another, and none is inside
180
+ * another. */
181
+ pagesDir: string;
182
+ /** Where `pd build` writes the storefront, relative to the root. Its own folder, apart from the workbench
183
+ * snapshot, and separate from `pagesDir` and `patternsDir` (see `pagesDir`). */
184
+ outDir: string;
185
+ /** The host name `pd dev` serves the storefront on. Default: `live.localhost`. */
186
+ host?: string;
187
+ /**
188
+ * Where the built workbench will be hosted: an absolute http(s) URL with no query or fragment, e.g.
189
+ * `https://workbench.example.com/`, or, for a workbench on the storefront's own host, a path such as
190
+ * `/workbench/` (then built with that base path, and its previews kept out of the storefront's
191
+ * `robots.txt`; an absolute URL on `url`'s origin counts as the same host too). Loaded with exactly
192
+ * one trailing slash, whether written with one or not; links from storefront pages to the workbench are
193
+ * this value followed by a path. Used by `pd build` only: in `pd dev` those links always go to
194
+ * `http://pd.localhost:<port>`, the workbench of the same process.
195
+ */
196
+ workbenchUrl?: string;
197
+ /**
198
+ * The site's address, such as `https://example.com/`: an absolute http(s) URL with no path (the storefront
199
+ * is served from the root of its host), no query or fragment; loaded as its origin followed by `/`. With it,
200
+ * pages get a canonical URL, Open Graph and JSON-LD, and `pd build` writes a sitemap; without it they are
201
+ * left out, and `pd build` says so once.
202
+ */
203
+ url?: string;
204
+ /** The language of every page, as `<html lang>` takes it (a BCP 47 tag such as `de-AT`). Default: `en`. */
205
+ lang?: string;
206
+ /**
207
+ * `false` keeps the whole site out of search engines (a staging site): every page gets `noindex`,
208
+ * `robots.txt` disallows everything, and there is no sitemap. Default: `true`.
209
+ */
210
+ index?: boolean;
211
+ /** The image a page shows in link previews when it names none of its own: a raster image in the assets
212
+ * (PNG, JPEG, GIF or WebP), such as `/assets/images/share.png`. */
213
+ image?: string;
214
+ /** This site's image encoding, over the project's `images`. */
215
+ images?: ImageSettings;
216
+ /**
217
+ * Values every page of the site shares, such as its title or the line in its footer. Every layout gets
218
+ * them as `site`; a page cannot set them. Which keys there are, and their types, is what the `site`
219
+ * property of `partials/base`'s schema declares: they are checked against it, not inferred.
220
+ *
221
+ * One key is Pattern Dump's: `navigations`, named lists of links (a footer's, a sidebar's), each entry a
222
+ * page, an external link or a group, nested to any depth. Their shape is fixed (StorefrontNavEntry) and
223
+ * checked by Pattern Dump; the skin's schema only lists the names it uses.
224
+ */
225
+ site?: Record<string, unknown> & {
226
+ navigations?: Record<string, StorefrontNavEntry[]>;
227
+ };
228
+ }
229
+ /**
230
+ * One entry of a declared navigation (`storefront.site.navigations.<name>`):
231
+ * - a page, by its path in the pages folder (`"guides/setup.md"`), labelled with its title unless `label`;
232
+ * - an external link: `href` is `https:`, `http:` or `mailto:`, or a path from the site's root (`/feed.xml`);
233
+ * - a group: a `label` and its `children`, entries of any of the three kinds.
234
+ */
235
+ export type StorefrontNavEntry = {
236
+ page: string;
237
+ label?: string;
238
+ } | {
239
+ href: string;
240
+ label: string;
241
+ } | {
242
+ label: string;
243
+ children: StorefrontNavEntry[];
244
+ };
245
+ /**
246
+ * What a project writes: the config, where `sourceDir`, `patternsDir` and `styles` may be left out (a site whose
247
+ * patterns and styles all come from pattern packages has none of its own). `defineConfig` and `validateConfig` fill
248
+ * them in (`withSourceDefaults`), so everything that reads a config gets all three.
249
+ */
250
+ export type PatternDumpConfigInput = Omit<PatternDumpConfig, "sourceDir" | "patternsDir" | "styles"> & Partial<Pick<PatternDumpConfig, "sourceDir" | "patternsDir" | "styles">>;
251
+ export declare function defineConfig(config: PatternDumpConfigInput): PatternDumpConfig;
@@ -1,4 +1,7 @@
1
- import type { PatternDumpConfig } from "./index.js";
1
+ import type { PatternDumpConfig, PatternDumpConfigInput } from "./index.js";
2
+ /** The closest of `known` to `key` when it is near enough to be a typo of it, else undefined. Used for
3
+ * "did you mean" in config problems, and by other checks that name an unknown key. */
4
+ export declare function suggest(key: string, known: string[]): string | undefined;
2
5
  /** A config that cannot be used, with a message written for the person editing it. The CLI prints
3
6
  * its message alone, without a stack trace. */
4
7
  export declare class ConfigError extends Error {
@@ -7,4 +10,19 @@ export declare class ConfigError extends Error {
7
10
  /** The problems with a loaded config, one line each; empty when it is valid. */
8
11
  export declare function configProblems(raw: unknown): string[];
9
12
  /** Check a loaded config and return it typed, or throw one error listing every problem. */
13
+ /**
14
+ * A config with its source folders filled in where it names none: `sourceDir` is "./src", and `patternsDir` and
15
+ * `styles.dir` are its `patterns` and `styles` folders. None of them has to exist: a site whose patterns and styles
16
+ * all come from pattern packages has none of its own. What the config names is kept as it is.
17
+ */
18
+ export declare function withSourceDefaults(config: PatternDumpConfigInput): PatternDumpConfig;
10
19
  export declare function validateConfig(raw: unknown, source: string): PatternDumpConfig;
20
+ /**
21
+ * The path of a workbench published on the storefront's own host, or undefined when it has a host of its own.
22
+ * Same-host means a root-relative `workbenchUrl` ("/workbench/"), or an absolute one on the origin of
23
+ * `storefront.url`. Reads a validated config, so both are already normalised (one trailing slash).
24
+ */
25
+ export declare function sameHostWorkbenchPath(storefront: {
26
+ workbenchUrl?: string;
27
+ url?: string;
28
+ } | undefined): string | undefined;
@@ -70,7 +70,7 @@ export interface TreeNode {
70
70
  children?: TreeNode[];
71
71
  }
72
72
  export type DiagnosticSeverity = "error" | "warning" | "info";
73
- export type DiagnosticKind = "RENDER_ERROR" | "UNFILLED_REQUIRED" | "SHAPE_MISMATCH" | "HTML_INVALID" | "CASCADE_WARNING" | "A11Y" | "HIGHLIGHT_FALLBACK" | "HANDLE_COLLISION" | "DATA_INVALID";
73
+ export type DiagnosticKind = "RENDER_ERROR" | "UNFILLED_REQUIRED" | "SHAPE_MISMATCH" | "HTML_INVALID" | "CASCADE_WARNING" | "A11Y" | "HIGHLIGHT_FALLBACK" | "HANDLE_COLLISION" | "DATA_INVALID" | "SCHEMA_INVALID";
74
74
  /** Fields every diagnostic carries, whichever engine produced it. */
75
75
  interface DiagnosticBase {
76
76
  severity: DiagnosticSeverity;
@@ -81,7 +81,10 @@ interface DiagnosticBase {
81
81
  ruleId?: string;
82
82
  /** JSON Schema / data pointer, e.g. "#/properties/label" — schema kinds only. */
83
83
  path?: string;
84
- /** Source-file position — RENDER_ERROR and DATA_INVALID, best-effort. */
84
+ /** Source-file position — RENDER_ERROR and DATA_INVALID, best-effort. `file` is never an absolute
85
+ * path: relative to the project root for the project's own files (`src/patterns/atoms/x/x.json`),
86
+ * `<package name>/<path in the package>` for a pattern package's (`@acme/ds/patterns/atoms/x/x.json`).
87
+ * Output built anywhere names a file the same way, and names no folder of the machine it ran on. */
85
88
  location?: {
86
89
  file?: string;
87
90
  line?: number;
@@ -464,6 +467,14 @@ export declare const PD_ROUTES: {
464
467
  readonly version: "/api/version";
465
468
  /** GET → ShellClientConfig */
466
469
  readonly config: "/api/config";
470
+ /** GET → StorefrontPagesResponse, on the storefront host. What `pd build` crawls for storefront pages. */
471
+ readonly pages: "/api/pages";
472
+ /** GET → the storefront's sitemap (XML), on the storefront host; only when `storefront.url` is set. */
473
+ readonly sitemap: "/sitemap.xml";
474
+ /** GET → robots.txt (text): the storefront's on the storefront host, the workbench's on every other host. */
475
+ readonly robots: "/robots.txt";
476
+ /** GET → the storefront's 404 page (HTML, status 404), on the storefront host. `pd build` writes it as is. */
477
+ readonly notFound: "/404.html";
467
478
  /** The shell bundle, stylesheet and vendored assets. */
468
479
  readonly mainJs: "/_pd/main.js";
469
480
  readonly mainCss: "/_pd/main.css";
@@ -474,6 +485,16 @@ export declare const PD_ROUTES: {
474
485
  /** Document-base layer (reset + font-face). */
475
486
  readonly baseCss: "/_pd/base.css";
476
487
  };
488
+ /**
489
+ * The two front ends of one `pd dev` process, told apart by the request's Host header. Both serve
490
+ * from `/`. Plain `localhost` and `127.0.0.1` also get the workbench. `storefront.host` in the config
491
+ * replaces the storefront default, which is a host of pd dev's only when there is a storefront. A request
492
+ * for any other host is refused (403).
493
+ */
494
+ export declare const PD_HOSTS: {
495
+ readonly storefront: "live.localhost";
496
+ readonly workbench: "pd.localhost";
497
+ };
477
498
  /** Prefixes of the parameterised routes. routes.ts matches with these; the builders emit them. */
478
499
  export declare const PD_ROUTE_PREFIX: {
479
500
  readonly component: "/api/component/";
@@ -484,6 +505,7 @@ export declare const PD_ROUTE_PREFIX: {
484
505
  readonly themeCss: "/_pd/themes/";
485
506
  readonly script: "/_pd/scripts/";
486
507
  readonly assets: "/assets/";
508
+ readonly images: "/_pd/images/";
487
509
  };
488
510
  /**
489
511
  * GET /api/component/:id[?variant=x] → ComponentDetailResponse (404 → ApiError).
@@ -496,6 +518,21 @@ export declare function componentCssUrl(id: ComponentId, version?: number | stri
496
518
  export declare function themeCssUrl(name: string): string;
497
519
  /** A compiled behaviour script (esbuild, from scriptsDir). `version` is a cache-buster. */
498
520
  export declare function scriptUrl(id: string, version?: number | string): string;
521
+ /**
522
+ * A responsive image variant, served immutable: WebP, `width` pixels wide, of the asset `name` (its path under the
523
+ * assets folder without its extension, e.g. "images/harbour"). `hash` is the variant's key, the first 10 hex
524
+ * digits of a SHA-256 over the source's bytes and what the variant is encoded with, so the URL changes whenever
525
+ * its bytes would. Each segment of `name` is encoded.
526
+ */
527
+ export declare function imageUrl(name: string, hash: string, width: number): string;
528
+ /** An image variant URL's parts (`name` decoded), or null when the path is not one: an empty, "." or ".."
529
+ * segment, a segment that doesn't decode, a hash that isn't 10 hex digits, a width that isn't a whole number
530
+ * from 1, written without leading zeros. */
531
+ export declare function parseImageUrl(pathname: string): {
532
+ name: string;
533
+ hash: string;
534
+ width: number;
535
+ } | null;
499
536
  /** How the shell switches component themes in the preview: toggle `data-theme` on one set of
500
537
  * linked theme stylesheets, or swap a single theme stylesheet (for bare-`:root` theme sets). */
501
538
  export type ThemeSwitch = "attribute" | "stylesheet";
@@ -509,6 +546,31 @@ export interface ShellClientConfig {
509
546
  /** The configured default theme — what an un-themed preview actually shows. "" when none. */
510
547
  defaultTheme: string;
511
548
  }
549
+ /** One storefront page: a markdown file in `storefront.pagesDir`, at the route its path defines. */
550
+ export interface StorefrontPage {
551
+ /** Root-relative and slash-wrapped: "/" for pages/index.md, "/guides/setup/" for pages/guides/setup.md. */
552
+ route: string;
553
+ /** The page's `title`, or its route when it has none. */
554
+ title: string;
555
+ /** The layout it renders with: the component `layouts/<layout>`. */
556
+ layout: string;
557
+ /** Old routes that forward to this page (frontmatter `redirectFrom`), each root-relative and slash-wrapped.
558
+ * `pd build` writes a forwarding page at each. Absent when the page has none. */
559
+ redirectFrom?: string[];
560
+ }
561
+ /** A nav entry. Top-level pages and folders; a folder's pages are its children (one level only). */
562
+ export interface StorefrontNavItem {
563
+ route: string;
564
+ title: string;
565
+ /** The page's `description` from its frontmatter; absent when it has none. */
566
+ description?: string;
567
+ children?: StorefrontNavItem[];
568
+ }
569
+ /** GET /api/pages — every storefront page and the nav, in nav order. */
570
+ export interface StorefrontPagesResponse {
571
+ pages: StorefrontPage[];
572
+ nav: StorefrontNavItem[];
573
+ }
512
574
  /** The body of every JSON error response. */
513
575
  export interface ApiError {
514
576
  error: string;
@@ -563,7 +625,9 @@ export declare function staticPathFor(liveUrl: string): string;
563
625
  * Protocol version of this file's wire shapes — HTTP responses AND socket messages.
564
626
  *
565
627
  * Bump it whenever a shape changes incompatibly (a field removed, renamed or retyped; an HMR arm
566
- * removed). Adding an optional field does not need a bump. The server announces it three ways —
628
+ * removed). Adding an optional field does not need a bump, and neither does adding an HMR arm: a shell
629
+ * bundled before the arm existed receives it, matches none of its handlers and does nothing. The
630
+ * server announces it three ways —
567
631
  * the `hello` socket message, the `X-PD-Protocol` response header, and `VersionInfo.protocol` —
568
632
  * and the shell compares it against the value it was bundled with, so a stale shell talking to a
569
633
  * newer server says so instead of misbehaving silently. Same idea as GRAPH_ARTIFACT_VERSION.
@@ -571,6 +635,20 @@ export declare function staticPathFor(liveUrl: string): string;
571
635
  export declare const PD_PROTOCOL_VERSION = 1;
572
636
  /** Response header carrying PD_PROTOCOL_VERSION on every dev-server response. */
573
637
  export declare const PD_PROTOCOL_HEADER = "X-PD-Protocol";
638
+ /**
639
+ * The live-reload generation a `pd dev` response was rendered from: `<boot>.<n>`, where `<boot>` names the
640
+ * server process and `<n>` counts the reload messages it has sent. Sent by a live server on every response
641
+ * (this header), in `hello`, and on every reload message (content-swap, css-link-swap, tree-changed,
642
+ * page-changed), bumped when such a message is sent, after the change's state is in place. A request takes
643
+ * it before anything else, so a document rendered while a change landed carries the generation from before
644
+ * it. A client that later sees a newer one knows its document may predate a change it never heard about
645
+ * (sent while its socket was not open, or while its fetch was in flight) and fetches it again. Absent when
646
+ * the server has no live reload (`pd build`). Additive: no protocol bump.
647
+ */
648
+ export declare const PD_GENERATION_HEADER = "X-PD-Generation";
649
+ /** True when generation `a` is older than `b`: from an earlier server process, or a lower count. Either
650
+ * absent compares as not behind (a server without live reload never makes a client refetch). */
651
+ export declare function generationBehind(a: string | undefined | null, b: string | undefined | null): boolean;
574
652
  /**
575
653
  * HMR socket messages, discriminated on `type`:
576
654
  * - hello : sent once per connection — carries PD_PROTOCOL_VERSION.
@@ -579,6 +657,8 @@ export declare const PD_PROTOCOL_HEADER = "X-PD-Protocol";
579
657
  * - error : a render or build failure, surfaced on the Console.
580
658
  * - tree-changed : a component was added or removed → re-fetch the tree.
581
659
  * - collision : a handle collision quarantined a component → surfaced on the Console.
660
+ * - page-changed : a storefront page (or the nav) changed → storefront pages reload. The
661
+ * workbench shell receives it too and ignores it.
582
662
  *
583
663
  * NOTE: A11yResult / meta.a11y were removed in M4.11. Rendered a11y findings are produced
584
664
  * client-side (axe in the live iframe) and do not flow through the HMR channel.
@@ -602,10 +682,20 @@ export interface CssLinkSwapMessage {
602
682
  * Only components with non-empty CSS output are included.
603
683
  */
604
684
  orderedHrefs?: string[];
685
+ /**
686
+ * Present (true) when the component's CSS was empty before this change and is not now. A component with
687
+ * no CSS gets no link (the link set holds linkable components only), so a document rendered while it
688
+ * was empty has no link for it to update.
689
+ */
690
+ cssBecameNonEmpty?: true;
691
+ /** The generation this message bumped the server to (PD_GENERATION_HEADER); set by the server. */
692
+ generation?: string;
605
693
  }
606
694
  /** Fired when a .hbs is added or removed — client re-fetches /api/tree to rebuild the nav. */
607
695
  export interface TreeChangedMessage {
608
696
  type: "tree-changed";
697
+ /** The generation this message bumped the server to (PD_GENERATION_HEADER); set by the server. */
698
+ generation?: string;
609
699
  }
610
700
  /** M6: a handle collision was detected at discovery time. Both paths are named so the author
611
701
  * can resolve the conflict (rename or move a file). The tree remains operational; the
@@ -616,15 +706,27 @@ export interface CollisionMessage {
616
706
  incumbentPath: string;
617
707
  quarantinedPath: string;
618
708
  }
619
- export type HmrMessage = HelloMessage | ContentSwapMessage | ErrorMessage | CssLinkSwapMessage | TreeChangedMessage | CollisionMessage;
709
+ /** A storefront page changed. `route` names the page ("/guides/setup/"); null means a page was added
710
+ * or removed, so the nav — and with it every page — changed. */
711
+ export interface PageChangedMessage {
712
+ type: "page-changed";
713
+ route: string | null;
714
+ /** The generation this message bumped the server to (PD_GENERATION_HEADER); set by the server. */
715
+ generation?: string;
716
+ }
717
+ export type HmrMessage = HelloMessage | ContentSwapMessage | ErrorMessage | CssLinkSwapMessage | TreeChangedMessage | CollisionMessage | PageChangedMessage;
620
718
  /** First frame on every connection. Lets the shell detect a protocol mismatch with the server. */
621
719
  export interface HelloMessage {
622
720
  type: "hello";
623
721
  protocol: number;
722
+ /** The current live-reload generation (PD_GENERATION_HEADER). */
723
+ generation?: string;
624
724
  }
625
725
  export interface ContentSwapMessage {
626
726
  type: "content-swap";
627
727
  componentId: ComponentId;
728
+ /** The generation this message bumped the server to (PD_GENERATION_HEADER); set by the server. */
729
+ generation?: string;
628
730
  }
629
731
  export interface ErrorMessage {
630
732
  type: "error";
@@ -0,0 +1,33 @@
1
+ /** What the embedding page does with a decision. Only `write` is required. */
2
+ export interface HeightFitAdapter {
3
+ /** Set the frame's height, in CSS px. */
4
+ write(frame: HTMLIFrameElement, height: number): void;
5
+ /** Whether this frame content-fits its height at all (a full-page preview does not). Default: yes. */
6
+ fits?(frame: HTMLIFrameElement): boolean;
7
+ /** Called once when a frame's height turns out to be circular, after it was set back to its steady height. */
8
+ onCircular?(frame: HTMLIFrameElement): void;
9
+ /** Called when a frame's history is forgotten (its document was replaced or restyled). */
10
+ onReset?(frame: HTMLIFrameElement): void;
11
+ }
12
+ /** Per frame: the height last applied, the last height that held still, and how many growing ticks followed in a row. */
13
+ export interface HeightState {
14
+ last: number;
15
+ steady: number;
16
+ run: number;
17
+ }
18
+ export interface HeightFitter {
19
+ /** Apply a pd-height measured in `frame`'s document. Returns true when the frame's height was written. */
20
+ accept(frame: HTMLIFrameElement, height: number): boolean;
21
+ /** Forget a frame's height history: growth detection starts over. */
22
+ reset(frame: HTMLIFrameElement): void;
23
+ /** If `event` is a pd-height message from one of `frames`, that frame and its height; otherwise null. */
24
+ fromMessage(event: MessageEvent<unknown>, frames: Iterable<HTMLIFrameElement>): {
25
+ frame: HTMLIFrameElement;
26
+ height: number;
27
+ } | null;
28
+ /** Whether `measured` continues a run of growth against the heights we imposed — the loop's signature. */
29
+ isCircularGrowth(previous: HeightState | undefined, measured: number): boolean;
30
+ }
31
+ export declare function createHeightFitter(adapter: HeightFitAdapter): HeightFitter;
32
+ /** The circular-growth rule, for callers without a fitter. The same function every fitter uses. */
33
+ export declare function isCircularGrowth(previous: HeightState | undefined, measured: number): boolean;
@@ -16,7 +16,33 @@ export interface RenderInput {
16
16
  variant?: string;
17
17
  /** Live-control overrides; highest precedence in the cascade. */
18
18
  data?: Record<string, unknown>;
19
+ /** Which image settings the variant URLs this render writes are keyed with: "site" for a storefront page and the
20
+ * previews its host serves (`storefront.images` over `images`), else the project's (`images`). */
21
+ images?: "site";
19
22
  }
23
+ /**
24
+ * What an image in the assets is, for `{{pd-image}}`: looked up by its URL (`/assets/…`), synchronously, from an
25
+ * index built before rendering. Sizes are as the image is displayed (EXIF orientation applied).
26
+ * - `variants`: a raster image, served as WebP variants; `variantUrl(width, settings)` is a variant's URL.
27
+ * - `single`: one file, as it is (an SVG, or a raster image pd dev shows as it is when it can't encode).
28
+ * - `unavailable`: a raster image that can't be served as variants; `reason` says why (the build fails the page).
29
+ */
30
+ export type ImageSource = {
31
+ kind: "variants";
32
+ width: number;
33
+ height: number;
34
+ variantUrl(width: number, settings?: "site"): string;
35
+ } | {
36
+ kind: "single";
37
+ url: string;
38
+ width: number;
39
+ height: number;
40
+ } | {
41
+ kind: "unavailable";
42
+ reason: string;
43
+ };
44
+ /** Looks an asset up by its URL; undefined when the assets have no image there. */
45
+ export type ImageLookup = (src: string) => ImageSource | undefined;
20
46
  export interface RenderMeta {
21
47
  componentId: string;
22
48
  variant?: string;
@@ -117,6 +143,8 @@ export type Render = (input: RenderInput) => Promise<RenderOutput>;
117
143
  export interface RendererOptions {
118
144
  /** Root containing the atomic pattern folders (atoms/, molecules/, ...). */
119
145
  patternsDir: string;
146
+ /** The images in the assets, for `{{pd-image}}`. Without it, `{{pd-image}}` fails the render. */
147
+ images?: ImageLookup;
120
148
  /** Every patterns folder, project first, when pattern packages are in use. Takes precedence over
121
149
  * `patternsDir` for discovery. */
122
150
  patternRoots?: PatternRoot[];
@@ -158,6 +186,13 @@ export interface RendererOptions {
158
186
  * the real M6 file-as-component walk, never the old collectComponents folder-per-component walk.
159
187
  */
160
188
  discoveryMap?: DiscoveryMap;
189
+ /**
190
+ * How a diagnostic names a source file (`location.file`): project-relative, or prefixed with its
191
+ * package's name (see `Diagnostic` in dev-protocol). The dev-server supplies it, since only it knows
192
+ * the project root and each package's folder. Without it, or when it returns an absolute path, the
193
+ * renderer names the file relative to its pattern root. A diagnostic never carries an absolute path.
194
+ */
195
+ displayPath?: (absPath: string) => string;
161
196
  }
162
197
  /** Factory: a configured renderer closes over its source root, keeping `render()` pure. */
163
198
  export type CreateRenderer = (options: RendererOptions) => Render;
@@ -292,3 +327,4 @@ export interface ShellBrandConfig {
292
327
  /** Ordered list of CSS custom-property names used as swatch slots. */
293
328
  export type SwatchSlots = string[];
294
329
  export * from "./dev-protocol.js";
330
+ export * from "./iframe-fit.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pattern-dump",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.3.0",
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",
@@ -16,7 +16,11 @@
16
16
  "static-site",
17
17
  "htwoo"
18
18
  ],
19
- "author": "Stefan Bauer <stefan.bauer@n8d.at> (https://n8d.at)",
19
+ "author": {
20
+ "name": "Stefan Bauer (N8D e.U.)",
21
+ "email": "stefan.bauer@n8d.at",
22
+ "url": "https://n8d.at"
23
+ },
20
24
  "license": "MIT",
21
25
  "homepage": "https://github.com/n8design/pattern-dump#readme",
22
26
  "repository": {
@@ -51,10 +55,11 @@
51
55
  "templates",
52
56
  "README.md",
53
57
  "LICENSE",
58
+ "THIRD-PARTY-NOTICES.md",
54
59
  "CHANGELOG.md"
55
60
  ],
56
61
  "engines": {
57
- "node": ">=20.10"
62
+ "node": ">=22.22"
58
63
  },
59
64
  "dependencies": {
60
65
  "@shikijs/transformers": "^4.1.0",
@@ -71,6 +76,7 @@
71
76
  "postcss": "^8.5.15",
72
77
  "postcss-selector-parser": "^7.1.1",
73
78
  "sass": "^1.100.0",
79
+ "sharp": "0.35.5",
74
80
  "shiki": "^4.1.0",
75
81
  "source-map": "^0.6.1",
76
82
  "ws": "^8.21.0"
@@ -8,6 +8,7 @@ npm run audit # static defects across every component
8
8
  npm run typecheck # type-check the behaviour scripts
9
9
  npm run build:scripts # compile them to lib/scripts (JavaScript + type declarations)
10
10
  ```
11
+ __PD_STOREFRONT_README__
11
12
 
12
13
  ## Layout
13
14
 
@@ -1,3 +1,4 @@
1
1
  node_modules
2
2
  dist
3
3
  lib
4
+ __PD_STOREFRONT_GITIGNORE__
@@ -30,6 +30,7 @@ export default defineConfig({
30
30
  // Each package describes its patterns, styles and themes in its package.json ("patternDump").
31
31
  // packages: ["@n8d/htwoo-patterns"],
32
32
 
33
+ __PD_STOREFRONT__
33
34
  shell: {
34
35
  brand: { title: __PD_BRAND_JSON__ },
35
36
  },
@@ -2,6 +2,10 @@
2
2
 
3
3
  // The document defaults every preview sits on — the base layer named by `documentBase` in the config.
4
4
  // Fallbacks keep a preview readable before a theme is chosen.
5
+ //
6
+ // `:where(body)` has no specificity, so these are defaults only: a pattern package's own document
7
+ // rules (hTWOo's body background and text colour, which follow its themes) override them even
8
+ // though the project's base rules come later in the cascade.
5
9
 
6
10
  *,
7
11
  *::before,
@@ -9,7 +13,7 @@
9
13
  box-sizing: border-box;
10
14
  }
11
15
 
12
- body {
16
+ :where(body) {
13
17
  margin: 0;
14
18
  font-family: tokens.$font-family;
15
19
  line-height: 1.5;
@@ -0,0 +1,53 @@
1
+ # Builds the website and the workbench, and publishes both to GitHub Pages:
2
+ # the website at the root of your domain, the workbench under /workbench/.
3
+ # Before the first run: Settings → Pages → Source: "GitHub Actions", and your custom domain: see "Publish it" in the README.
4
+ name: Pages
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+ pages: write
14
+ id-token: write
15
+
16
+ # One deploy at a time; a deploy that has started is never cancelled halfway.
17
+ concurrency:
18
+ group: pages
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ build:
23
+ runs-on: ubuntu-24.04
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+ with:
27
+ # The full history: a layout that shows page dates needs it, and pd build refuses a shallow clone then.
28
+ fetch-depth: 0
29
+ - uses: actions/setup-node@v7
30
+ with:
31
+ node-version: 24
32
+ cache: npm
33
+ - run: npm ci
34
+ - run: npm run build
35
+ # pd build writes the website (site/) and the workbench (dist/) side by side; Pages takes one folder.
36
+ - run: |
37
+ mkdir _site
38
+ cp -R site/. _site/
39
+ cp -R dist _site/workbench
40
+ - uses: actions/configure-pages@v6
41
+ - uses: actions/upload-pages-artifact@v5
42
+ with:
43
+ path: _site
44
+
45
+ deploy:
46
+ needs: build
47
+ runs-on: ubuntu-24.04
48
+ environment:
49
+ name: github-pages
50
+ url: ${{ steps.deployment.outputs.page_url }}
51
+ steps:
52
+ - id: deployment
53
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,2 @@
1
+ <!-- Drawn by Stefan Bauer, part of this repository, MIT. -->
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#0b5cad"/><path d="M9 9h14v4H9zm0 6h14v4H9zm0 6h9v4H9z" fill="#fff"/></svg>
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: About
3
+ description: What is in this starter, and where it lives.
4
+ order: 1
5
+ ---
6
+
7
+ The pages are in `pages/`. What they look like is the skin, written as ordinary components so the
8
+ workbench shows each part of it:
9
+
10
+ - `src/patterns/layouts/page`: the layout every page renders with
11
+ - `src/patterns/partials/base`: the frame around it, with the header, navigation and footer
12
+ - `src/patterns/partials/head`: what the skin adds to every page's `<head>`
13
+ - `src/patterns/components/note`: a component the pages show
14
+
15
+ The site's name and footer are `storefront.site` in `pattern-dump.config.ts`.
@@ -0,0 +1,24 @@
1
+ ---
2
+ title: Home
3
+ description: A website written in markdown, next to the design system it shows.
4
+ ---
5
+
6
+ This page is `pages/index.md`. Every markdown file in `pages/` is a page, at the address its path
7
+ names: `pages/about.md` is [/about/](/about/).
8
+
9
+ ## A live example
10
+
11
+ A `pattern` fence shows a component from the workbench, in a frame of its own, so its own styles
12
+ render it and nothing on this page can restyle it:
13
+
14
+ ```pattern components/note
15
+ ```
16
+
17
+ ## A component with your values
18
+
19
+ A `pd-` tag places a component with the values you give it. They are checked against the component's
20
+ schema, `src/patterns/components/note/note.schema.json`, before the page is shown:
21
+
22
+ <pd-components-note tone="tip" heading="Try breaking it">
23
+ Change `tone="tip"` to `tone="tips"` and save. The page shows what is wrong, and where.
24
+ </pd-components-note>
@@ -0,0 +1,4 @@
1
+ <aside class="note{{#if tone}} note-{{tone}}{{/if}}" role="note">
2
+ <p class="note-heading">{{heading}}</p>
3
+ <div class="note-text">{{{text}}}</div>
4
+ </aside>
@@ -0,0 +1,4 @@
1
+ {
2
+ "heading": "A note",
3
+ "text": "<p>Something worth knowing, next to what it is about.</p>"
4
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ title: Note
3
+ ---
4
+
5
+ A short aside. A page places one with values of its own:
6
+
7
+ ```md
8
+ <pd-components-note tone="tip" heading="Try breaking it">
9
+ Text, in **markdown**.
10
+ </pd-components-note>
11
+ ```