pattern-dump 0.1.0-alpha.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 (47) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +92 -0
  3. package/dist/dev-ui/main.css +1825 -0
  4. package/dist/dev-ui/main.js +3054 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +7 -0
  7. package/dist/pd.js +4759 -0
  8. package/dist/types/config/index.d.ts +145 -0
  9. package/dist/types/contracts/dev-protocol.d.ts +624 -0
  10. package/dist/types/contracts/index.d.ts +294 -0
  11. package/package.json +76 -0
  12. package/templates/base/README.md +77 -0
  13. package/templates/base/_gitignore +3 -0
  14. package/templates/base/pattern-dump.config.ts +36 -0
  15. package/templates/base/src/scripts/globals.d.ts +6 -0
  16. package/templates/base/src/styles/base/_tokens.scss +8 -0
  17. package/templates/base/src/styles/base/document.scss +18 -0
  18. package/templates/base/themes/dark.theme.scss +8 -0
  19. package/templates/base/themes/light.theme.scss +8 -0
  20. package/templates/base/tsconfig.json +19 -0
  21. package/templates/examples/src/patterns/atoms/button/button.hbs +1 -0
  22. package/templates/examples/src/patterns/atoms/button/button.json +1 -0
  23. package/templates/examples/src/patterns/atoms/button/button.md +21 -0
  24. package/templates/examples/src/patterns/atoms/button/button.schema.json +11 -0
  25. package/templates/examples/src/patterns/atoms/button/button~icon.hbs +5 -0
  26. package/templates/examples/src/patterns/atoms/button/button~secondary.json +1 -0
  27. package/templates/examples/src/patterns/atoms/index.md +6 -0
  28. package/templates/examples/src/patterns/index.md +3 -0
  29. package/templates/examples/src/patterns/molecules/index.md +6 -0
  30. package/templates/examples/src/patterns/molecules/search-field/input/input.hbs +4 -0
  31. package/templates/examples/src/patterns/molecules/search-field/input/input.json +1 -0
  32. package/templates/examples/src/patterns/molecules/search-field/input/input.md +8 -0
  33. package/templates/examples/src/patterns/molecules/search-field/input/input.schema.json +10 -0
  34. package/templates/examples/src/patterns/molecules/search-field/search-field.hbs +5 -0
  35. package/templates/examples/src/patterns/molecules/search-field/search-field.json +1 -0
  36. package/templates/examples/src/patterns/molecules/search-field/search-field.md +21 -0
  37. package/templates/examples/src/patterns/molecules/search-field/search-field.schema.json +11 -0
  38. package/templates/examples/src/patterns/pages/example/example.hbs +11 -0
  39. package/templates/examples/src/patterns/pages/example/example.json +8 -0
  40. package/templates/examples/src/patterns/pages/example/example.md +11 -0
  41. package/templates/examples/src/patterns/pages/example/example.schema.json +14 -0
  42. package/templates/examples/src/patterns/pages/index.md +6 -0
  43. package/templates/examples/src/scripts/search-field.ts +16 -0
  44. package/templates/examples/src/styles/atoms/button.scss +36 -0
  45. package/templates/examples/src/styles/molecules/search-field/input.scss +21 -0
  46. package/templates/examples/src/styles/molecules/search-field.scss +13 -0
  47. package/templates/examples/src/styles/pages/example.scss +28 -0
@@ -0,0 +1,624 @@
1
+ /** A component's canonical identity: its path relative to `patterns/`. e.g. "atoms/button". */
2
+ export type ComponentId = string;
3
+ /** Top-level atomic category — the first path segment. */
4
+ export type Category = string;
5
+ export type ComponentStatus = "draft" | "stable" | "deprecated";
6
+ /**
7
+ * Preview layout mode (M4.2). How the component sits in the stage:
8
+ * - "centered": inner <main> hugs content, height-fits, stage centers it (atoms/molecules/organisms)
9
+ * - "full": inner <main> fills the stage width AND height, top-aligned (pages/templates)
10
+ * This is the RESOLVED mode (category default ← category index.md frontmatter ← component override),
11
+ * so the shell renders directly without re-deriving precedence. Width is pure CSS; height is measured
12
+ * for centered mode only (full mode fills).
13
+ */
14
+ export type LayoutMode = "centered" | "full";
15
+ /** A named data set applied over defaults — a state or a documentation example. */
16
+ export interface VariantRef {
17
+ /** The `~variant` token, e.g. "secondary", "disabled". Absent = component defaults. */
18
+ name: string;
19
+ }
20
+ /**
21
+ * GET /api/tree (M4.3 — recursive)
22
+ * The sidebar navigation tree: a recursive hierarchy reflecting REAL folder depth
23
+ * (category → intermediate group folders → components), e.g. atoms/input/text-input.
24
+ *
25
+ * Sibling order, at every level including the roots — alphabetical unless the docs override it:
26
+ * 1. the parent folder's `index.md` `order:` LIST of child names (`patterns/index.md` for roots)
27
+ * puts the listed children first, in list order; unknown names are ignored;
28
+ * 2. otherwise a child's own numeric `order:` WEIGHT (a component's `.md`, a group's `index.md`)
29
+ * sorts ascending, weighted before unweighted;
30
+ * 3. then alphabetical by path segment, numeric-aware (`item2` before `item10`).
31
+ * Only the leading `---` frontmatter block is read. The client renders this order as served.
32
+ */
33
+ export interface ComponentTree {
34
+ /** Top-level nodes (atoms, molecules, …), in the sibling order above. */
35
+ roots: TreeNode[];
36
+ }
37
+ /**
38
+ * One uniform node for every position in the tree. Three real cases fall out of one shape:
39
+ * - group folder — kind "group", has children, no component of its own (e.g. "atoms/input")
40
+ * - leaf component — kind "component", no children (e.g. "atoms/button")
41
+ * - component with children — kind "component" + children (e.g. "molecules/field" owning "field/input")
42
+ * Depth is unrestricted; nesting is informational lineage, not an addressing boundary.
43
+ */
44
+ export interface TreeNode {
45
+ /** Full path under patterns/ — folder or component:
46
+ * "atoms" | "atoms/input" | "atoms/input/text-input". */
47
+ path: string;
48
+ /** Display title from frontmatter. Without one, a component falls back to its last path segment
49
+ * as-is; a group folder to that segment made readable ("quick-links" → "Quick links"). */
50
+ title: string;
51
+ /** "group" = folder with no component of its own; "component" = renderable (may also have children). */
52
+ kind: "group" | "component";
53
+ /** Present when kind === "component". */
54
+ status?: ComponentStatus;
55
+ /** Resolved layout mode; present when kind === "component". */
56
+ layout?: LayoutMode;
57
+ /** Lineage marker. Private components are nested under their owner and hidden by default in the tree. */
58
+ visibility?: "public" | "private";
59
+ /** The pattern package (npm name) this component comes from; absent for the project's own. Present
60
+ * only on component nodes — a group's source follows from its components. */
61
+ source?: string;
62
+ /** Has an overview page? TRUE for a group/category folder with ≥1 public component anywhere
63
+ * beneath it — `index.md` is optional. Suppressed by `overviews: false` in config (all folders)
64
+ * or `overview: false` in the folder's `index.md` (that folder). FALSE for component nodes
65
+ * (M4.3) — a component's "all variants" gallery is M4.4, a separate concept. */
66
+ hasOverview: boolean;
67
+ /** Sub-folders / sub-components; absent or empty for a leaf. */
68
+ children?: TreeNode[];
69
+ }
70
+ export type DiagnosticSeverity = "error" | "warning" | "info";
71
+ export type DiagnosticKind = "RENDER_ERROR" | "UNFILLED_REQUIRED" | "SHAPE_MISMATCH" | "HTML_INVALID" | "CASCADE_WARNING" | "A11Y" | "HIGHLIGHT_FALLBACK" | "HANDLE_COLLISION";
72
+ /** Fields every diagnostic carries, whichever engine produced it. */
73
+ interface DiagnosticBase {
74
+ severity: DiagnosticSeverity;
75
+ message: string;
76
+ /** Rule id within the engine (e.g. html-validate "element-permitted-content"). A11yDiagnostic
77
+ * sets this to the axe rule id; html-validate sets it to the failing rule; absent for kinds that
78
+ * have no finer rule than their `kind` (RENDER_ERROR, HANDLE_COLLISION, schema kinds). */
79
+ ruleId?: string;
80
+ /** JSON Schema / data pointer, e.g. "#/properties/label" — schema kinds only. */
81
+ path?: string;
82
+ /** Source-file position — RENDER_ERROR, best-effort. */
83
+ location?: {
84
+ file?: string;
85
+ line?: number;
86
+ column?: number;
87
+ };
88
+ }
89
+ /** A finding from any engine other than axe. */
90
+ export interface GeneralDiagnostic extends DiagnosticBase {
91
+ kind: Exclude<DiagnosticKind, "A11Y">;
92
+ source: "render" | "schema" | "html" | "cascade" | "highlight" | "discovery";
93
+ }
94
+ /** Per-node axe-core finding. One diagnostic per failing DOM node. */
95
+ export interface A11yDiagnostic extends DiagnosticBase {
96
+ kind: "A11Y";
97
+ source: "axe";
98
+ ruleId: string;
99
+ target: string;
100
+ nodeHtml: string;
101
+ help: string;
102
+ helpUrl: string;
103
+ /** Populated only when ruleId === "color-contrast". Source: nodes[n].any[0].data. */
104
+ contrast?: {
105
+ fg: string;
106
+ bg: string;
107
+ ratio: number;
108
+ expected: number;
109
+ };
110
+ /** True when sourced from axe's `incomplete` array — axe could not confirm pass/fail.
111
+ * Absent/false = confirmed violation. Confirmed and needs-review are distinct certainty levels. */
112
+ needsReview?: boolean;
113
+ }
114
+ /**
115
+ * Any diagnostic — a union discriminated on `kind` (and equivalently `source`): checking
116
+ * `d.kind === "A11Y"` narrows to A11yDiagnostic, so consumers never cast to reach axe fields.
117
+ */
118
+ export type Diagnostic = GeneralDiagnostic | A11yDiagnostic;
119
+ /** The detection engine that produced a finding — a DISTINCT dimension from the rule id. Roll-up by
120
+ * engine ("all axe", "all html-validation") is a field filter on this, never a string-prefix match. */
121
+ export type DiagnosticEngine = "axe" | "html-validate" | "schema" | "render" | "discovery";
122
+ /** Provenance tag. `SUBJECT-DEBT` = a real defect in the system under test (the common case; the
123
+ * subject owns its templates). `PD-GAP` = PD's own detector/pipeline is at fault, not the subject.
124
+ * Default is always SUBJECT-DEBT; PD-GAP is applied only via an explicit allowlist, never inferred
125
+ * from a diagnostic kind (a RENDER_ERROR is the subject's template debt by default). */
126
+ export type DiagnosticTag = "SUBJECT-DEBT" | "PD-GAP";
127
+ /** A component's render outcome — metadata on the node, NEVER itself a defect occurrence. A component
128
+ * that renders empty for want of project data (data-masked) or a base synthesized from variants is
129
+ * legitimately `blank`; blank annotates findings (populated rules aren't over-claimed on empty
130
+ * output) but is not counted as a defect. `error` = the template threw (a real defect, also emitted
131
+ * as a RENDER_ERROR occurrence). */
132
+ export type RenderOutcome = "ok" | "blank" | "error";
133
+ /** A rule node. `engine` and `ruleId` are two DISTINCT dimensions, never concatenated into one id. */
134
+ export interface RuleNode {
135
+ engine: DiagnosticEngine;
136
+ ruleId: string;
137
+ /** Optional composed label for display only (e.g. "axe:color-contrast"). Not an identity. */
138
+ displayKey?: string;
139
+ }
140
+ /** A component node — keyed on the discovery id (`collapseComponentId` form). */
141
+ export interface DiagnosticComponentNode {
142
+ id: ComponentId;
143
+ render: RenderOutcome;
144
+ }
145
+ /** An occurrence edge: this rule fired on this component. References the rule by the (engine, ruleId)
146
+ * pair — the same two distinct dimensions, not a concatenated node id. */
147
+ export interface DiagnosticOccurrence {
148
+ engine: DiagnosticEngine;
149
+ ruleId: string;
150
+ component: ComponentId;
151
+ severity: DiagnosticSeverity;
152
+ message: string;
153
+ /** DOM path of the failing node, when the engine provides one (axe target / html-validate selector). */
154
+ domPath?: string;
155
+ tag: DiagnosticTag;
156
+ }
157
+ /** The project-wide diagnostics graph. Storage IS the graph; all tables are views over `occurrences`. */
158
+ export interface DiagnosticsGraph {
159
+ rules: RuleNode[];
160
+ components: DiagnosticComponentNode[];
161
+ occurrences: DiagnosticOccurrence[];
162
+ /** ISO timestamp stamped by the caller after the pure build returns (build has no clock access). */
163
+ generatedAt?: string;
164
+ /** The subject's commit SHA when it has one (pinned in the report). */
165
+ subjectSha?: string;
166
+ }
167
+ /** The §0 scope boundary — embedded verbatim in every report/view so the artifact states its own
168
+ * limits. Corrected for the sweep's real coverage: the rendered-contrast (needsRendering) axe tier
169
+ * is a named exclusion (it needs browser layout the static jsdom sweep cannot produce). */
170
+ export declare const SCOPE_BOUNDARY = "## Scope \u2014 the boundary is part of the artifact\n\n**Covers** \u2014 machine-detectable, static, single-render (no browser layout):\n- **HTML content-model** (html-validate) \u2014 nesting/permitted-content violations.\n- **Structural a11y** (axe, alwaysOn tier) \u2014 labels, names, ARIA, roles, image-alt.\n- **Schema** \u2014 required/shape violations against `component.schema.json`.\n- **Render outcome** \u2014 blank / error / ok (metadata, never itself a defect).\n\n**Deliberately NOT covered \u2014 named boundaries, not misses:**\n- **Rendered-contrast (axe needsRendering tier)** \u2014 contrast, aria-hidden-focus, duplicate-id-aria.\n These require real browser layout/paint; the static jsdom sweep cannot produce them. The\n per-component panel shows them (live iframe); this aggregate does not. Fewer rule types here is a\n scope boundary, not a gap.\n- **Structural/graph findings** \u2014 dead subtrees, a shared-base \"component\", flattened variant families.\n Those are the style graph's lens, not this one.\n- **PD reporting gaps** \u2014 PD under-claiming (e.g. missing provenance) is not a subject defect.\n- **Interactive / behavioural** \u2014 focus traps, keyboard flow, escape handling. Single static render only.\n- **Data-masked** \u2014 a component that renders empty for want of data won't throw the violations it would\n when populated; findings are annotated with render outcome wherever it is blank.\n\nA component with zero findings is **not \"clean\" \u2014 it is \"clean of static machine-detectable defects.\"**\nThat is the honest claim; never \"clean\".";
171
+ /** Per-rule roll-up: blast radius (distinct components) primary, occurrences secondary. */
172
+ export interface AuditRuleRollup {
173
+ engine: string;
174
+ ruleId: string;
175
+ components: number;
176
+ occurrences: number;
177
+ tags: string[];
178
+ }
179
+ export interface AuditComponentRollup {
180
+ id: string;
181
+ render: RenderOutcome;
182
+ occurrences: Array<{
183
+ engine: string;
184
+ ruleId: string;
185
+ tag: DiagnosticTag;
186
+ message: string;
187
+ domPath?: string;
188
+ }>;
189
+ }
190
+ /** The `pd audit` report as structured data — the single rollup every output format serializes.
191
+ * All roll-ups (headline numbers, rule blast-radius table, per-component list) are computed ONCE in
192
+ * `buildAuditRollup`; `md`/`text`/`json` (and the workbench facet) are renderers over this object,
193
+ * never independent string builders. A CI consumer reading the `json` gets `scope` (the §0 boundary)
194
+ * + `headline` alongside the count, never a bare number. */
195
+ export interface AuditRollup {
196
+ subjectSha?: string;
197
+ /** The headline sentence, plain (no markdown emphasis); renderers wrap/position it. */
198
+ headline: string;
199
+ totals: {
200
+ components: number;
201
+ affected: number;
202
+ cleanPct: number;
203
+ defects: number;
204
+ };
205
+ renderOutcomes: {
206
+ ok: number;
207
+ blank: number;
208
+ error: number;
209
+ };
210
+ rulesByBlast: AuditRuleRollup[];
211
+ perComponent: AuditComponentRollup[];
212
+ /** The §0 scope boundary, verbatim (SCOPE_BOUNDARY). Carried in every format. */
213
+ scope: string;
214
+ }
215
+ /** Build the structured rollup from the graph — the numbers exist as data here, not only as strings.
216
+ * PURE: same graph → same rollup, no I/O or clock. The one builder both the CLI report renderers and
217
+ * the workbench audit facet call. */
218
+ export declare function buildAuditRollup(graph: DiagnosticsGraph, opts?: {
219
+ subjectSha?: string;
220
+ }): AuditRollup;
221
+ /**
222
+ * GET /api/version
223
+ * Build identity of the running dev server, so a human or script (scripts/check-server-fresh.mjs)
224
+ * can tell whether the process answering is behind the artifacts on disk. Frozen at boot.
225
+ */
226
+ export interface VersionInfo {
227
+ /** Wall-clock start, ISO-8601. */
228
+ startedAt: string;
229
+ /** mtime (ms) of the dev-server bundle this process actually loaded, read at startup. */
230
+ serverBundleMtime: number | null;
231
+ /** PD_PROTOCOL_VERSION the server was built with. */
232
+ protocol: number;
233
+ }
234
+ /**
235
+ * GET /api/component/:id
236
+ * Everything the panel + preview need for one component. The shell never reads the
237
+ * filesystem — this is the whole picture.
238
+ */
239
+ export interface ComponentDetail {
240
+ id: ComponentId;
241
+ title: string;
242
+ description: string;
243
+ category: Category;
244
+ status: ComponentStatus;
245
+ /** If status === "deprecated". */
246
+ deprecatedUse?: string;
247
+ /** Resolved preview layout mode (M4.2) — see LayoutMode. Always present (a default always applies). */
248
+ layout: LayoutMode;
249
+ /**
250
+ * Full preview HTML document for the default variant, ready to assign directly to
251
+ * `iframe.srcdoc`. Non-default variants are fetched on demand via `previewUrl()`.
252
+ * Root-relative asset links (`/_pd/*.css`, `/assets/...`) resolve because an unsandboxed
253
+ * srcdoc iframe inherits the parent origin; the static build will supply a `<base href>`
254
+ * via `buildPreviewDocument`'s `baseHref` parameter instead.
255
+ *
256
+ * Always present in live dev-server responses. Optional in the type because
257
+ * `buildComponentDetail` returns the rest of the detail without it — the HTTP handler
258
+ * computes and appends it before sending the JSON payload.
259
+ */
260
+ document?: string;
261
+ /** Switchable variants for the toolbar. The default (no variant) is always available implicitly. */
262
+ variants: VariantRef[];
263
+ /** Source files, for the source tab's HBS/HTML toggle and any future viewers. */
264
+ source: ComponentSource;
265
+ /** Rendered markdown documentation for the docs tab. Absent if no `.md`.
266
+ * M4.1: fenced code blocks inside are Shiki-highlighted at render time (class-based HTML);
267
+ * `pattern` embed blocks are NOT highlighted — they render the real component via render(). */
268
+ doc?: string;
269
+ /**
270
+ * Composition-aggregated behaviour script ids and CDN URLs from `requireScript` frontmatter.
271
+ * Bare ids are served at /_pd/scripts/<id>.js (ESbuild-compiled from scriptsDir).
272
+ * Absolute URLs (contain `://`) are CDN refs injected as-is.
273
+ * Deduped, preserving declaration order. Absent when none declared in the component tree.
274
+ */
275
+ requireScripts?: string[];
276
+ /**
277
+ * Composition-aggregated SVG symbol set ids from `requireAssets` frontmatter.
278
+ * Each id resolves to a symbol set SVG in the project assets dir, inlined hidden at the top
279
+ * of the preview body to enable same-document `<use href="#id">` with currentColor inheritance.
280
+ * Deduped. Absent when none declared.
281
+ */
282
+ requireAssets?: string[];
283
+ /** Composition + style relationships for this component (M5). Discriminated on `available`. */
284
+ relationships: Relationships;
285
+ /** Per-render diagnostics for the selected component + variant (M4.9). Empty = clean render. */
286
+ diagnostics: Diagnostic[];
287
+ }
288
+ export interface ComponentSource {
289
+ /** Raw Handlebars template text. Always present (`.hbs` is the only required file).
290
+ * Raw is the copy source of truth — the copy button copies this, never the highlighted markup. */
291
+ hbs: string;
292
+ /** Render-time Shiki-highlighted HBS as class-based HTML (M4.1). For display in the source tab. */
293
+ hbsHighlighted: string;
294
+ /** Raw default-variant data, if a `component.json` exists (optional per M2).
295
+ * Per-variant data comes via `dataSourceUrl` (not carried in the payload). */
296
+ json?: string;
297
+ /** Highlighted JSON, present whenever `json` is. */
298
+ jsonHighlighted?: string;
299
+ /** Raw `component.schema.json`, if the file exists. Component-level; does not vary with variant. */
300
+ schema?: string;
301
+ /** Shiki-highlighted schema JSON. Present whenever `schema` is. */
302
+ schemaHighlighted?: string;
303
+ /** Raw markdown source, if present. (Highlighted fenced blocks live in the RENDERED doc — the
304
+ * `doc` field on ComponentDetail — not here; this is the raw `.md` for reference/copy.) */
305
+ md?: string;
306
+ }
307
+ /**
308
+ * The relationships facet payload (M5) — two layers of edges:
309
+ * - composition (template): uses / usedBy — from the flat-partial registry.
310
+ * Three DISTINCT style relationships — kept separable in the graph, do not conflate:
311
+ * - style (component class-borrowing): styleUses — components whose CSS classes appear in this
312
+ * component's own markup (.hbs), attributed via a class→owner index built from compiled
313
+ * CSS source maps. styleUsedBy is intentionally omitted (it's the transpose of every
314
+ * component's styleUses; recompute on demand for impact analysis).
315
+ * - style (@use dependency): styleUsesModules — shared base/token/mixin modules this component's
316
+ * SCSS @use/@forward-imports. This is a SCSS dependency, not a worn class. Convention:
317
+ * components must not @use another component's SCSS; if violated the target degrades to a
318
+ * plain module path here (non-navigable).
319
+ * - style (module class-borrowing): styleModules — shared/family modules that AUTHOR a class worn
320
+ * by this component's markup but belong to no component (the module-level analog of styleUses).
321
+ * These are the classes that would otherwise vanish into the base layer unattributed.
322
+ * - style (leftovers): inlineStyles, utilityClasses, unattributedClasses — honest accounting
323
+ * for classes that don't map to a known component or module.
324
+ *
325
+ * Determinism: arrays are sorted and deduplicated — byte-identical for an unchanged tree.
326
+ */
327
+ /** A shared SCSS module that belongs to no component (e.g. "00-base/tokens"). Non-navigable. */
328
+ export type StyleModuleId = string;
329
+ export interface RelationshipsAvailable {
330
+ /** Always true. The "not yet computed" placeholder arm this once discriminated was never produced
331
+ * and has been removed; the flag stays so existing payload readers keep working. */
332
+ available: true;
333
+ /** Components this one includes via template partials (composition). */
334
+ uses: ComponentId[];
335
+ /** Components that include this one (inverse composition). */
336
+ usedBy: ComponentId[];
337
+ /** Components whose CSS classes appear in this component's own markup (class-borrowing). */
338
+ styleUses: ComponentId[];
339
+ /** Shared SCSS modules this component's SCSS @use/@forward-imports — a dependency, not a worn
340
+ * class (non-navigable). */
341
+ styleUsesModules: StyleModuleId[];
342
+ /** Shared/family modules that authored a class worn by this component's markup but belong to no
343
+ * component — the module-level analog of styleUses (non-navigable). */
344
+ styleModules: StyleModuleId[];
345
+ /** True if markup contains style="" attributes or inline <style> blocks. */
346
+ inlineStyles: boolean;
347
+ /** True if any used class is owned by a registered utility source (e.g. Tailwind). */
348
+ utilityClasses: boolean;
349
+ /** CSS classes used in markup whose ownership is unrecognised (typos, dead, unregistered). */
350
+ unattributedClasses: string[];
351
+ }
352
+ export type Relationships = RelationshipsAvailable;
353
+ /**
354
+ * GET /api/overview/:path (M4.3 — was /api/category/:cat in M4.2)
355
+ * The overview page for a FOLDER — a top-level category ("atoms") or an intermediate group
356
+ * ("atoms/input"). The `index.md` (if any) plus the ordered list of the PUBLIC components in that
357
+ * folder's subtree, for the "show me all of these" gallery. Each item renders as its real component
358
+ * in its own isolated preview (via previewUrl), so the gallery is a list of <figure> illustrations.
359
+ *
360
+ * `index.md` is optional: with none, `doc` is absent and the gallery is auto-generated from
361
+ * `components` using the folder's default layout. Component nodes do NOT get a folder overview
362
+ * (their "all variants" gallery is M4.4); overviews belong to group/category folders.
363
+ */
364
+ export interface FolderOverview {
365
+ /** Folder path — "atoms" or "atoms/input". */
366
+ path: string;
367
+ /** Rendered `index.md` body, if the folder has one (M4.1 highlighting applies to its fences). */
368
+ doc?: string;
369
+ /** Resolved layout mode for this folder's previews (component overrides still win per item). */
370
+ layout: LayoutMode;
371
+ /** The public components in this folder's subtree, in tree order (pre-order, siblings as in
372
+ * ComponentTree) — the same order the Nav Rail shows. Each rendered live. */
373
+ components: FolderOverviewItem[];
374
+ }
375
+ export interface FolderOverviewItem {
376
+ id: ComponentId;
377
+ /** The pattern package this component comes from; absent for the project's own. */
378
+ source?: string;
379
+ title: string;
380
+ status: ComponentStatus;
381
+ /** Per-item resolved layout (usually the folder default; a component may override). */
382
+ layout: LayoutMode;
383
+ }
384
+ /**
385
+ * Returns the URL for a component's standalone preview document.
386
+ *
387
+ * Used for:
388
+ * - On-demand fetch of non-default variant documents (`fetch(previewUrl(id, v)).then(r=>r.text())`)
389
+ * - HMR content-swap re-fetch
390
+ * - Open-in-new-tab links
391
+ *
392
+ * The single-preview iframe is driven by `srcdoc`, NOT `src` — this URL is never assigned
393
+ * to `iframe.src`. Keeping the route active for the use cases above.
394
+ *
395
+ * `theme` bakes `data-theme` into the served `<body>` pre-paint, fixing the one-frame flash
396
+ * on non-default themes. Pass the current user theme so the iframe renders correctly without
397
+ * waiting for the `iframe.load` handler. An empty string or absent value uses the config default.
398
+ *
399
+ * GET /preview/:id → default-data, config-default theme
400
+ * GET /preview/:id?variant=x → variant document
401
+ * GET /preview/:id?theme=dark → config-default variant, dark theme pre-baked
402
+ * GET /preview/:id?variant=x&theme=dark → variant + theme
403
+ */
404
+ export declare function previewUrl(id: ComponentId, variant?: string, theme?: string): string;
405
+ /**
406
+ * The source tab's "rendered HTML" view (M4.1 — DECIDED).
407
+ *
408
+ * The rendered HTML is per-variant and HMR-volatile (it changes on every `content-swap`),
409
+ * so — unlike the static `.hbs`/`.json` source, which rides in ComponentSource — it is NOT
410
+ * carried in the once-fetched ComponentDetail. The shell fetches the Shiki-highlighted
411
+ * rendered fragment by URL, lazily, only when this view is selected, and re-fetches on a
412
+ * `content-swap` for the current variant. This mirrors the per-variant `previewUrl` model.
413
+ *
414
+ * GET /source/:id/rendered → highlighted rendered HTML for default data
415
+ * GET /source/:id/rendered?variant=x → highlighted rendered HTML for a variant
416
+ *
417
+ * Returns class-based highlighted HTML (same Shiki stylesheet as ComponentSource.*Highlighted).
418
+ * There is deliberately no `htmlHighlighted` field on ComponentSource.
419
+ */
420
+ export declare function renderedSourceUrl(id: ComponentId, variant?: string): string;
421
+ /**
422
+ * The data file for a component selection (JSON tab, M4.6).
423
+ *
424
+ * Per-variant data is HMR-volatile (it changes when the variant file changes), so it is NOT
425
+ * carried in the once-fetched ComponentDetail. The shell fetches the Shiki-highlighted data
426
+ * fragment lazily (only when the JSON tab is selected) and re-fetches on a `content-swap`.
427
+ * The default-variant data is also available via this route for consistency.
428
+ *
429
+ * GET /source/:id/data → highlighted default data (component.json)
430
+ * GET /source/:id/data?variant=x → highlighted variant data (component~x.json)
431
+ *
432
+ * Returns class-based highlighted HTML (same Shiki stylesheet as ComponentSource.*Highlighted).
433
+ */
434
+ export declare function dataSourceUrl(id: ComponentId, variant?: string): string;
435
+ /**
436
+ * Folder overview route (M4.3). Addressed by folder path, any depth.
437
+ * GET /api/overview/atoms → top-level category overview
438
+ * GET /api/overview/atoms/input → intermediate group overview
439
+ */
440
+ export declare function overviewUrl(path: string): string;
441
+ /** Fixed-path routes. */
442
+ export declare const PD_ROUTES: {
443
+ /** GET → ComponentTree */
444
+ readonly tree: "/api/tree";
445
+ /** GET → AuditResponse (500 → ApiError) */
446
+ readonly audit: "/api/audit";
447
+ /** GET → ThemeSummary[] */
448
+ readonly themes: "/api/themes";
449
+ /** GET → VersionInfo */
450
+ readonly version: "/api/version";
451
+ /** GET → ShellClientConfig */
452
+ readonly config: "/api/config";
453
+ /** The shell bundle, stylesheet and vendored assets. */
454
+ readonly mainJs: "/_pd/main.js";
455
+ readonly mainCss: "/_pd/main.css";
456
+ readonly axeCore: "/_pd/axe-core.js";
457
+ /** GET → RuleTiers */
458
+ readonly ruleTiers: "/_pd/rule-tiers.json";
459
+ readonly shikiCss: "/_pd/shiki.css";
460
+ /** Document-base layer (reset + font-face). */
461
+ readonly baseCss: "/_pd/base.css";
462
+ };
463
+ /** Prefixes of the parameterised routes. routes.ts matches with these; the builders emit them. */
464
+ export declare const PD_ROUTE_PREFIX: {
465
+ readonly component: "/api/component/";
466
+ readonly overview: "/api/overview/";
467
+ readonly preview: "/preview/";
468
+ readonly source: "/source/";
469
+ readonly componentCss: "/_pd/components/";
470
+ readonly themeCss: "/_pd/themes/";
471
+ readonly script: "/_pd/scripts/";
472
+ readonly assets: "/assets/";
473
+ };
474
+ /**
475
+ * GET /api/component/:id[?variant=x] → ComponentDetailResponse (404 → ApiError).
476
+ * A quarantined or failing component is still 200, carrying its diagnostic.
477
+ */
478
+ export declare function componentUrl(id: ComponentId, variant?: string): string;
479
+ /** Per-component own-origin CSS. `version` is a cache-buster (`?v=`), ignored by the server. */
480
+ export declare function componentCssUrl(id: ComponentId, version?: number | string): string;
481
+ /** Per-theme CSS. 404 when the theme is unknown or no themes dir is configured. */
482
+ export declare function themeCssUrl(name: string): string;
483
+ /** A compiled behaviour script (esbuild, from scriptsDir). `version` is a cache-buster. */
484
+ export declare function scriptUrl(id: string, version?: number | string): string;
485
+ /** How the shell switches component themes in the preview: toggle `data-theme` on one set of
486
+ * linked theme stylesheets, or swap a single theme stylesheet (for bare-`:root` theme sets). */
487
+ export type ThemeSwitch = "attribute" | "stylesheet";
488
+ /** GET /api/config — the server-declared settings the shell reads once at startup. */
489
+ export interface ShellClientConfig {
490
+ /** The project's `shell` config block, or null when it has none. */
491
+ shell: {
492
+ brand?: import("./index.js").ShellBrandConfig;
493
+ } | null;
494
+ themeSwitch: ThemeSwitch;
495
+ /** The configured default theme — what an un-themed preview actually shows. "" when none. */
496
+ defaultTheme: string;
497
+ }
498
+ /** The body of every JSON error response. */
499
+ export interface ApiError {
500
+ error: string;
501
+ }
502
+ /** GET /api/audit — the diagnostics graph, stamped at response time. */
503
+ export interface AuditResponse extends DiagnosticsGraph {
504
+ generatedAt: string;
505
+ /** Surgical HMR re-audits since startup — a debug instrument (rises by exactly 1 per .hbs edit,
506
+ * 0 per .scss edit). Not part of the graph; the leading underscore marks it as diagnostic. */
507
+ _liveReaudits: number;
508
+ }
509
+ /** GET /_pd/rule-tiers.json — the generated axe rule classification (packages/a11y classifier). */
510
+ export interface RuleTiers {
511
+ /** Rules the static (jsdom) sweep runs. */
512
+ alwaysOn: string[];
513
+ /** Rules whose verdict needs real browser layout — run only in the live iframe. */
514
+ needsRendering: string[];
515
+ /** Rules not yet classified. */
516
+ investigate: string[];
517
+ /** Provenance written by the classifier. */
518
+ _note?: string;
519
+ generatedAt?: string;
520
+ axeVersion?: string;
521
+ }
522
+ /** GET /api/component/:id — the live server always includes the preview document. */
523
+ export type ComponentDetailResponse = ComponentDetail & {
524
+ document: string;
525
+ };
526
+ /** How the shell document was served, inlined as `window.__pd` by whichever side produced it. */
527
+ export type ShellRuntime = {
528
+ mode: "live";
529
+ wsPort: number;
530
+ } | {
531
+ mode: "static";
532
+ /** Where the snapshot is hosted: "/" at a domain root, or "/sub/path/" (always slash-wrapped). */
533
+ basePath: string;
534
+ /** ISO timestamp of the build. */
535
+ builtAt: string;
536
+ /** The pattern-dump version that built it. */
537
+ pdVersion: string;
538
+ /** false when built without the corpus audit (`pd build --no-audit`): no api/audit.json exists
539
+ * and the shell offers no Audit tab. Absent means included. */
540
+ audit?: boolean;
541
+ };
542
+ /** "/pd", "pd/", "pd" → "/pd/"; "" and "/" → "/". */
543
+ export declare function normalizeBasePath(basePath: string | undefined): string;
544
+ /** The snapshot file (relative, no leading slash) that holds the response for a live URL.
545
+ * The result is a URL PATH, still percent-encoded where the live URL was (`brand%20blue.css`): the
546
+ * shell requests it as-is, a static host decodes it, so the build writes the DECODED name to disk. */
547
+ export declare function staticPathFor(liveUrl: string): string;
548
+ /**
549
+ * Protocol version of this file's wire shapes — HTTP responses AND socket messages.
550
+ *
551
+ * Bump it whenever a shape changes incompatibly (a field removed, renamed or retyped; an HMR arm
552
+ * removed). Adding an optional field does not need a bump. The server announces it three ways —
553
+ * the `hello` socket message, the `X-PD-Protocol` response header, and `VersionInfo.protocol` —
554
+ * and the shell compares it against the value it was bundled with, so a stale shell talking to a
555
+ * newer server says so instead of misbehaving silently. Same idea as GRAPH_ARTIFACT_VERSION.
556
+ */
557
+ export declare const PD_PROTOCOL_VERSION = 1;
558
+ /** Response header carrying PD_PROTOCOL_VERSION on every dev-server response. */
559
+ export declare const PD_PROTOCOL_HEADER = "X-PD-Protocol";
560
+ /**
561
+ * HMR socket messages, discriminated on `type`:
562
+ * - hello : sent once per connection — carries PD_PROTOCOL_VERSION.
563
+ * - content-swap : template / data / `.ts` change → re-fetch and swap the preview document.
564
+ * - css-link-swap : SCSS change → re-point the per-component `<link>` in the live iframe, no reload.
565
+ * - error : a render or build failure, surfaced on the Console.
566
+ * - tree-changed : a component was added or removed → re-fetch the tree.
567
+ * - collision : a handle collision quarantined a component → surfaced on the Console.
568
+ *
569
+ * NOTE: A11yResult / meta.a11y were removed in M4.11. Rendered a11y findings are produced
570
+ * client-side (axe in the live iframe) and do not flow through the HMR channel.
571
+ */
572
+ /**
573
+ * A CSS-only change rebuilt the affected component's origin file.
574
+ * The client finds the matching `<link data-pd-comp>` in the iframe and
575
+ * updates its href to the cache-busted URL — no srcdoc rewrite, no state loss.
576
+ */
577
+ export interface CssLinkSwapMessage {
578
+ type: "css-link-swap";
579
+ /** Component whose per-origin CSS file changed. */
580
+ componentId: ComponentId;
581
+ /** New URL, e.g. "/_pd/components/atoms/button.css?v=1717000000000". */
582
+ href: string;
583
+ /**
584
+ * Ordered closure hrefs for the currently-previewed component (`lastComponentId`).
585
+ * Absent when no preview is active. Each href is `/_pd/components/<id>.css?v=<timestamp>`,
586
+ * in DFS post-order (deepest dependency first). The client reconciles the preview head's
587
+ * pd-managed links against this list: add missing, remove dead, bump survivors.
588
+ * Only components with non-empty CSS output are included.
589
+ */
590
+ orderedHrefs?: string[];
591
+ }
592
+ /** Fired when a .hbs is added or removed — client re-fetches /api/tree to rebuild the nav. */
593
+ export interface TreeChangedMessage {
594
+ type: "tree-changed";
595
+ }
596
+ /** M6: a handle collision was detected at discovery time. Both paths are named so the author
597
+ * can resolve the conflict (rename or move a file). The tree remains operational; the
598
+ * quarantined component is excluded until the collision is resolved. */
599
+ export interface CollisionMessage {
600
+ type: "collision";
601
+ handle: string;
602
+ incumbentPath: string;
603
+ quarantinedPath: string;
604
+ }
605
+ export type HmrMessage = HelloMessage | ContentSwapMessage | ErrorMessage | CssLinkSwapMessage | TreeChangedMessage | CollisionMessage;
606
+ /** First frame on every connection. Lets the shell detect a protocol mismatch with the server. */
607
+ export interface HelloMessage {
608
+ type: "hello";
609
+ protocol: number;
610
+ }
611
+ export interface ContentSwapMessage {
612
+ type: "content-swap";
613
+ componentId: ComponentId;
614
+ }
615
+ export interface ErrorMessage {
616
+ type: "error";
617
+ /** Which pipeline failed — keeps build errors from masquerading as render errors. */
618
+ scope: "build" | "render";
619
+ /** Component-scoped where known (e.g. a missing partial in this component). */
620
+ componentId?: ComponentId;
621
+ /** A legible, already-formatted message (per M1: not a raw Handlebars stack trace). */
622
+ message: string;
623
+ }
624
+ export {};