@glw907/cairn-cms 0.11.0 → 0.14.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 (75) hide show
  1. package/dist/components/ComponentForm.svelte +33 -10
  2. package/dist/components/ComponentForm.svelte.d.ts.map +1 -1
  3. package/dist/components/IconPicker.svelte +53 -7
  4. package/dist/components/IconPicker.svelte.d.ts +7 -3
  5. package/dist/components/IconPicker.svelte.d.ts.map +1 -1
  6. package/dist/content/adapter.d.ts +4 -0
  7. package/dist/content/adapter.d.ts.map +1 -0
  8. package/dist/content/adapter.js +4 -0
  9. package/dist/content/concepts.js +2 -2
  10. package/dist/content/schema.d.ts +75 -0
  11. package/dist/content/schema.d.ts.map +1 -0
  12. package/dist/content/schema.js +72 -0
  13. package/dist/content/types.d.ts +30 -7
  14. package/dist/content/types.d.ts.map +1 -1
  15. package/dist/content/validate.d.ts +5 -3
  16. package/dist/content/validate.d.ts.map +1 -1
  17. package/dist/content/validate.js +14 -7
  18. package/dist/delivery/content-index.d.ts +8 -0
  19. package/dist/delivery/content-index.d.ts.map +1 -1
  20. package/dist/delivery/content-index.js +17 -8
  21. package/dist/delivery/index.d.ts +5 -1
  22. package/dist/delivery/index.d.ts.map +1 -1
  23. package/dist/delivery/index.js +2 -0
  24. package/dist/delivery/seo-fields.d.ts +22 -0
  25. package/dist/delivery/seo-fields.d.ts.map +1 -0
  26. package/dist/delivery/seo-fields.js +32 -0
  27. package/dist/delivery/site-index.d.ts +2 -2
  28. package/dist/delivery/site-index.d.ts.map +1 -1
  29. package/dist/delivery/site-index.js +16 -18
  30. package/dist/delivery/site-indexes.d.ts +26 -0
  31. package/dist/delivery/site-indexes.d.ts.map +1 -0
  32. package/dist/delivery/site-indexes.js +22 -0
  33. package/dist/index.d.ts +9 -3
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +5 -2
  36. package/dist/render/component-grammar.d.ts +7 -0
  37. package/dist/render/component-grammar.d.ts.map +1 -1
  38. package/dist/render/component-grammar.js +27 -8
  39. package/dist/render/component-validate.js +3 -3
  40. package/dist/render/glyph.d.ts +4 -1
  41. package/dist/render/glyph.d.ts.map +1 -1
  42. package/dist/render/glyph.js +6 -2
  43. package/dist/render/registry.d.ts +23 -5
  44. package/dist/render/registry.d.ts.map +1 -1
  45. package/dist/render/registry.js +6 -0
  46. package/dist/render/rehype-dispatch.d.ts +1 -5
  47. package/dist/render/rehype-dispatch.d.ts.map +1 -1
  48. package/dist/render/rehype-dispatch.js +71 -19
  49. package/dist/render/remark-directives.d.ts +1 -1
  50. package/dist/render/remark-directives.d.ts.map +1 -1
  51. package/dist/render/remark-directives.js +37 -0
  52. package/dist/sveltekit/public-routes.d.ts +3 -0
  53. package/dist/sveltekit/public-routes.d.ts.map +1 -1
  54. package/dist/sveltekit/public-routes.js +9 -2
  55. package/package.json +1 -1
  56. package/src/lib/components/ComponentForm.svelte +33 -10
  57. package/src/lib/components/IconPicker.svelte +53 -7
  58. package/src/lib/content/adapter.ts +10 -0
  59. package/src/lib/content/concepts.ts +2 -2
  60. package/src/lib/content/schema.ts +133 -0
  61. package/src/lib/content/types.ts +30 -7
  62. package/src/lib/content/validate.ts +10 -7
  63. package/src/lib/delivery/content-index.ts +25 -8
  64. package/src/lib/delivery/index.ts +5 -1
  65. package/src/lib/delivery/seo-fields.ts +43 -0
  66. package/src/lib/delivery/site-index.ts +15 -16
  67. package/src/lib/delivery/site-indexes.ts +52 -0
  68. package/src/lib/index.ts +8 -2
  69. package/src/lib/render/component-grammar.ts +34 -10
  70. package/src/lib/render/component-validate.ts +3 -3
  71. package/src/lib/render/glyph.ts +6 -2
  72. package/src/lib/render/registry.ts +27 -5
  73. package/src/lib/render/rehype-dispatch.ts +67 -20
  74. package/src/lib/render/remark-directives.ts +39 -1
  75. package/src/lib/sveltekit/public-routes.ts +12 -2
@@ -29,23 +29,31 @@ function asTags(value) {
29
29
  }
30
30
  /** Build a concept's index from its raw files and normalized descriptor. */
31
31
  export function createContentIndex(files, descriptor) {
32
+ const problems = [];
32
33
  const entries = files.map((file) => {
33
34
  const id = idFromFilename(basename(file.path));
34
35
  const slug = slugFromId(id, descriptor.routing.dated ? descriptor.datePrefix : null);
35
- const { frontmatter, body } = parseMarkdown(file.raw);
36
- const date = asDate(frontmatter.date);
36
+ const { frontmatter: raw, body } = parseMarkdown(file.raw);
37
+ const date = asDate(raw.date);
38
+ const draft = raw.draft === true;
39
+ // Validate once at build. The cheap summary stays raw-derived and robust; the typed detail
40
+ // frontmatter carries the normalized data on success, the raw frontmatter on failure. A
41
+ // failure is recorded, not thrown, so the query surface does not explode on construction.
42
+ const result = descriptor.validate(raw, body);
43
+ if (!result.ok)
44
+ problems.push({ id, draft, errors: result.errors });
37
45
  return {
38
46
  id,
39
47
  slug,
40
48
  permalink: permalink(descriptor, { id, slug, date }),
41
- title: asString(frontmatter.title) ?? id,
49
+ title: asString(raw.title) ?? id,
42
50
  date,
43
- updated: asDate(frontmatter.updated),
44
- tags: asTags(frontmatter.tags),
45
- excerpt: deriveExcerpt(body, { description: asString(frontmatter.description) }),
51
+ updated: asDate(raw.updated),
52
+ tags: asTags(raw.tags),
53
+ excerpt: deriveExcerpt(body, { description: asString(raw.description) }),
46
54
  wordCount: wordCount(body),
47
- draft: frontmatter.draft === true,
48
- frontmatter: frontmatter,
55
+ draft,
56
+ frontmatter: (result.ok ? result.data : raw),
49
57
  body,
50
58
  };
51
59
  });
@@ -82,5 +90,6 @@ export function createContentIndex(files, descriptor) {
82
90
  older: i < list.length - 1 ? summarize(list[i + 1]) : undefined,
83
91
  };
84
92
  },
93
+ problems: () => problems,
85
94
  };
86
95
  }
@@ -1,7 +1,9 @@
1
1
  export { createContentIndex, fromGlob } from './content-index.js';
2
- export type { RawFile, ContentSummary, ContentEntry, ContentIndex } from './content-index.js';
2
+ export type { RawFile, ContentSummary, ContentEntry, ContentIndex, ContentProblem } from './content-index.js';
3
3
  export { createSiteIndex } from './site-index.js';
4
4
  export type { SiteIndex, ConceptIndex } from './site-index.js';
5
+ export { createSiteIndexes } from './site-indexes.js';
6
+ export type { SiteIndexes, SiteGlobs } from './site-indexes.js';
5
7
  export { siteDescriptors } from './site-descriptors.js';
6
8
  export { deriveExcerpt, wordCount } from './excerpt.js';
7
9
  export { buildRssFeed, buildJsonFeed } from './feeds.js';
@@ -11,6 +13,8 @@ export type { SitemapUrl } from './sitemap.js';
11
13
  export { buildRobots } from './robots.js';
12
14
  export { buildSeoMeta } from './seo.js';
13
15
  export type { SeoInput, SeoMeta } from './seo.js';
16
+ export { readSeoFields, resolveImageUrl } from './seo-fields.js';
17
+ export type { SeoFields } from './seo-fields.js';
14
18
  export { paginate } from './paginate.js';
15
19
  export type { Page } from './paginate.js';
16
20
  export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './responses.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,YAAY,EACV,gBAAgB,EAChB,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,SAAS,GACV,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,YAAY,EACV,gBAAgB,EAChB,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,SAAS,GACV,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
@@ -5,12 +5,14 @@
5
5
  // github, or email, so importing it does not pull the server backend into a public bundle.
6
6
  export { createContentIndex, fromGlob } from './content-index.js';
7
7
  export { createSiteIndex } from './site-index.js';
8
+ export { createSiteIndexes } from './site-indexes.js';
8
9
  export { siteDescriptors } from './site-descriptors.js';
9
10
  export { deriveExcerpt, wordCount } from './excerpt.js';
10
11
  export { buildRssFeed, buildJsonFeed } from './feeds.js';
11
12
  export { buildSitemap } from './sitemap.js';
12
13
  export { buildRobots } from './robots.js';
13
14
  export { buildSeoMeta } from './seo.js';
15
+ export { readSeoFields, resolveImageUrl } from './seo-fields.js';
14
16
  export { paginate } from './paginate.js';
15
17
  export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './responses.js';
16
18
  export { jsonLdScript } from './json-ld.js';
@@ -0,0 +1,22 @@
1
+ /** The head fields a concept can carry in frontmatter. Each is optional and omitted when absent.
2
+ * `author` is article-scoped downstream: the head builder emits `article:author` only for a dated
3
+ * entry, so an `author` on an undated Page is read here but not rendered. */
4
+ export interface SeoFields {
5
+ description?: string;
6
+ image?: string;
7
+ robots?: string;
8
+ author?: string;
9
+ }
10
+ /** Read the known SEO head fields off an entry's normalized frontmatter. Keeps a present string,
11
+ * trimmed, and omits an absent, empty, or non-string value. Trimming the stored value keeps a stray
12
+ * `robots: " noindex "` from reaching the head tag with surrounding whitespace. The field must be
13
+ * declared in the concept's schema to survive the validate-once read; an undeclared key is not on the
14
+ * normalized frontmatter. */
15
+ export declare function readSeoFields(frontmatter: Record<string, unknown>): SeoFields;
16
+ /** Resolve an author-supplied image path to an absolute URL against the site origin. An absolute or
17
+ * protocol-relative URL passes through; a root-relative path anchors to the origin; a malformed
18
+ * string returns undefined rather than throwing at build. The sites use a bare-domain origin, so a
19
+ * bare path also anchors to the origin root; against a sub-path origin it would resolve relative to
20
+ * that path, per the WHATWG URL rules. */
21
+ export declare function resolveImageUrl(image: string, origin: string): string | undefined;
22
+ //# sourceMappingURL=seo-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo-fields.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/seo-fields.ts"],"names":[],"mappings":"AAKA;;8EAE8E;AAC9E,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;;8BAI8B;AAC9B,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAO7E;AAED;;;;2CAI2C;AAC3C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjF"}
@@ -0,0 +1,32 @@
1
+ // cairn-cms: the SEO head fields read at the cross-concept boundary (schema-source-of-truth design,
2
+ // Plan 3). The catch-all route resolves any concept by request path, so the entry's frontmatter is
3
+ // typed Record<string, unknown>; this reads the known head fields by name and coerces. Kept apart
4
+ // from seo.ts (the head builder) so reading frontmatter and building the head stay distinct concerns.
5
+ const KEYS = ['description', 'image', 'robots', 'author'];
6
+ /** Read the known SEO head fields off an entry's normalized frontmatter. Keeps a present string,
7
+ * trimmed, and omits an absent, empty, or non-string value. Trimming the stored value keeps a stray
8
+ * `robots: " noindex "` from reaching the head tag with surrounding whitespace. The field must be
9
+ * declared in the concept's schema to survive the validate-once read; an undeclared key is not on the
10
+ * normalized frontmatter. */
11
+ export function readSeoFields(frontmatter) {
12
+ const fields = {};
13
+ for (const key of KEYS) {
14
+ const value = frontmatter[key];
15
+ if (typeof value === 'string' && value.trim() !== '')
16
+ fields[key] = value.trim();
17
+ }
18
+ return fields;
19
+ }
20
+ /** Resolve an author-supplied image path to an absolute URL against the site origin. An absolute or
21
+ * protocol-relative URL passes through; a root-relative path anchors to the origin; a malformed
22
+ * string returns undefined rather than throwing at build. The sites use a bare-domain origin, so a
23
+ * bare path also anchors to the origin root; against a sub-path origin it would resolve relative to
24
+ * that path, per the WHATWG URL rules. */
25
+ export function resolveImageUrl(image, origin) {
26
+ try {
27
+ return new URL(image, origin).href;
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
@@ -25,8 +25,8 @@ export interface SiteIndex {
25
25
  }
26
26
  /**
27
27
  * Union per-concept indexes into a site-level resolver. Throws on a duplicate permalink and,
28
- * unless `validate` is `false`, on any entry whose frontmatter fails its concept's validator,
29
- * so malformed content fails the build instead of shipping.
28
+ * unless `validate` is `false`, on any non-draft entry whose frontmatter fails its concept's
29
+ * validator, so malformed content fails the build instead of shipping.
30
30
  */
31
31
  export declare function createSiteIndex(concepts: ConceptIndex[], opts?: {
32
32
  validate?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"site-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/site-index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAErF,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACpD,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IACpF,uGAAuG;IACvG,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9B,mEAAmE;IACnE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC9C,0EAA0E;IAC1E,GAAG,IAAI,cAAc,EAAE,CAAC;CACzB;AA2BD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,CAmCtG"}
1
+ {"version":3,"file":"site-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/site-index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAErF,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACpD,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IACpF,uGAAuG;IACvG,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9B,mEAAmE;IACnE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC9C,0EAA0E;IAC1E,GAAG,IAAI,cAAc,EAAE,CAAC;CACzB;AAqBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,CAwCtG"}
@@ -2,34 +2,32 @@
2
2
  function normalizePath(path) {
3
3
  return path.length > 1 ? path.replace(/\/+$/, '') : path;
4
4
  }
5
- /** Validate every entry (drafts included) against its concept, aggregating failures. */
6
- function validateAll(concepts) {
5
+ /** Collect non-draft validation failures across concepts from each index's recorded verdicts. */
6
+ function siteProblems(concepts) {
7
7
  const problems = [];
8
8
  for (const { descriptor, index } of concepts) {
9
- for (const summary of index.all({ includeDrafts: true })) {
10
- const entry = index.byId(summary.id);
11
- if (!entry)
12
- continue;
13
- const result = descriptor.validate(entry.frontmatter, entry.body);
14
- if (!result.ok) {
15
- for (const [field, message] of Object.entries(result.errors)) {
16
- problems.push(`${descriptor.dir}/${summary.id}: ${field}: ${message}`);
17
- }
9
+ for (const problem of index.problems()) {
10
+ if (problem.draft)
11
+ continue; // a half-finished draft never ships, so it does not fail the build
12
+ for (const [field, message] of Object.entries(problem.errors)) {
13
+ problems.push(`${descriptor.dir}/${problem.id}: ${field}: ${message}`);
18
14
  }
19
15
  }
20
16
  }
21
- if (problems.length > 0) {
22
- throw new Error(`site index: ${problems.length} invalid frontmatter field(s):\n ${problems.join('\n ')}`);
23
- }
17
+ return problems;
24
18
  }
25
19
  /**
26
20
  * Union per-concept indexes into a site-level resolver. Throws on a duplicate permalink and,
27
- * unless `validate` is `false`, on any entry whose frontmatter fails its concept's validator,
28
- * so malformed content fails the build instead of shipping.
21
+ * unless `validate` is `false`, on any non-draft entry whose frontmatter fails its concept's
22
+ * validator, so malformed content fails the build instead of shipping.
29
23
  */
30
24
  export function createSiteIndex(concepts, opts = {}) {
31
- if (opts.validate !== false)
32
- validateAll(concepts);
25
+ if (opts.validate !== false) {
26
+ const problems = siteProblems(concepts);
27
+ if (problems.length > 0) {
28
+ throw new Error(`site index: ${problems.length} invalid frontmatter field(s):\n ${problems.join('\n ')}`);
29
+ }
30
+ }
33
31
  const byPath = new Map();
34
32
  const byId = new Map();
35
33
  for (const { descriptor, index } of concepts) {
@@ -0,0 +1,26 @@
1
+ import type { CairnAdapter, ConceptConfig } from '../content/types.js';
2
+ import type { Infer } from '../content/schema.js';
3
+ import type { SiteConfig } from '../nav/site-config.js';
4
+ import type { ContentIndex } from './content-index.js';
5
+ import type { SiteIndex } from './site-index.js';
6
+ /** A per-concept raw glob record (`{ path: raw }`) keyed by concept id, from `import.meta.glob`. */
7
+ export type SiteGlobs<A extends CairnAdapter> = {
8
+ [K in keyof A['content']]?: Record<string, string>;
9
+ };
10
+ /** The typed per-concept indexes plus the cross-concept `site` resolver. A concept literally named
11
+ * `site` is not supported, since `site` is the reserved resolver key. */
12
+ export type SiteIndexes<A extends CairnAdapter> = {
13
+ [K in keyof A['content']]: ContentIndex<NonNullable<A['content'][K]> extends ConceptConfig<infer S> ? Infer<S> : Record<string, unknown>>;
14
+ } & {
15
+ readonly site: SiteIndex;
16
+ };
17
+ /**
18
+ * Build typed per-concept indexes and a site resolver from one adapter. Pass the per-concept raw
19
+ * globs as `{ posts: import.meta.glob('...?raw', { eager: true }), ... }`; Vite needs the literal
20
+ * glob at the call site, so the engine cannot glob on the site's behalf. `validate: false` opts out
21
+ * of the build gate, exactly as on `createSiteIndex`.
22
+ */
23
+ export declare function createSiteIndexes<const A extends CairnAdapter>(adapter: A, config: SiteConfig, globs: SiteGlobs<A>, opts?: {
24
+ validate?: boolean;
25
+ }): SiteIndexes<A>;
26
+ //# sourceMappingURL=site-indexes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-indexes.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/site-indexes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAgB,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE/D,oGAAoG;AACpG,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,YAAY,IAAI;KAC7C,CAAC,IAAI,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CACnD,CAAC;AAEF;0EAC0E;AAC1E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,YAAY,IAAI;KAC/C,CAAC,IAAI,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,YAAY,CACrC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACjG;CACF,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEjC;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,CAAC,SAAS,YAAY,EAC5D,OAAO,EAAE,CAAC,EACV,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EACnB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC,WAAW,CAAC,CAAC,CAAC,CAYhB"}
@@ -0,0 +1,22 @@
1
+ import { siteDescriptors } from './site-descriptors.js';
2
+ import { createContentIndex, fromGlob } from './content-index.js';
3
+ import { createSiteIndex } from './site-index.js';
4
+ /**
5
+ * Build typed per-concept indexes and a site resolver from one adapter. Pass the per-concept raw
6
+ * globs as `{ posts: import.meta.glob('...?raw', { eager: true }), ... }`; Vite needs the literal
7
+ * glob at the call site, so the engine cannot glob on the site's behalf. `validate: false` opts out
8
+ * of the build gate, exactly as on `createSiteIndex`.
9
+ */
10
+ export function createSiteIndexes(adapter, config, globs, opts = {}) {
11
+ const descriptors = siteDescriptors(adapter, config);
12
+ const byConcept = {};
13
+ const conceptIndexes = [];
14
+ for (const descriptor of descriptors) {
15
+ const record = globs[descriptor.id] ?? {};
16
+ const index = createContentIndex(fromGlob(record), descriptor);
17
+ byConcept[descriptor.id] = index;
18
+ conceptIndexes.push({ descriptor, index });
19
+ }
20
+ const site = createSiteIndex(conceptIndexes, opts);
21
+ return { ...byConcept, site };
22
+ }
package/dist/index.d.ts CHANGED
@@ -6,7 +6,9 @@ export type { CairnAdapter, ConceptConfig, FrontmatterField, TextField, Textarea
6
6
  export { CONCEPT_ROUTING, normalizeConcepts, findConcept } from './content/concepts.js';
7
7
  export { composeRuntime } from './content/compose.js';
8
8
  export { frontmatterFromForm, dateInputValue, serializeMarkdown, parseMarkdown, } from './content/frontmatter.js';
9
- export { validateFields } from './content/validate.js';
9
+ export { defineFields } from './content/schema.js';
10
+ export { defineAdapter } from './content/adapter.js';
11
+ export type { ConceptSchema, Infer, InferFields, DefineFieldsOptions, StandardInput, StandardSchemaV1 } from './content/schema.js';
10
12
  export { isValidId, idFromFilename, filenameFromId, slugify, slugFromId, composeDatedId, } from './content/ids.js';
11
13
  export type { DatePrefix } from './content/ids.js';
12
14
  export { defineRegistry, emptyValues } from './render/registry.js';
@@ -20,7 +22,7 @@ export type { ReferenceOptions } from './render/component-reference.js';
20
22
  export { glyph } from './render/glyph.js';
21
23
  export type { IconSet } from './render/glyph.js';
22
24
  export { remarkDirectiveStamp } from './render/remark-directives.js';
23
- export { rehypeDispatch, isElement, strProp, iconSpan, splitHead, cardShell, markFirstList, } from './render/rehype-dispatch.js';
25
+ export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, markFirstList, } from './render/rehype-dispatch.js';
24
26
  export type { MakeIcon } from './render/rehype-dispatch.js';
25
27
  export { createRenderer } from './render/pipeline.js';
26
28
  export type { RendererOptions } from './render/pipeline.js';
@@ -34,9 +36,11 @@ export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree,
34
36
  export type { NavNode, SiteConfig } from './nav/site-config.js';
35
37
  export { permalink } from './content/permalink.js';
36
38
  export { createContentIndex, fromGlob } from './delivery/content-index.js';
37
- export type { RawFile, ContentSummary, ContentEntry, ContentIndex, } from './delivery/content-index.js';
39
+ export type { RawFile, ContentSummary, ContentEntry, ContentIndex, ContentProblem, } from './delivery/content-index.js';
38
40
  export { createSiteIndex } from './delivery/site-index.js';
39
41
  export type { SiteIndex, ConceptIndex } from './delivery/site-index.js';
42
+ export { createSiteIndexes } from './delivery/site-indexes.js';
43
+ export type { SiteIndexes, SiteGlobs } from './delivery/site-indexes.js';
40
44
  export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
41
45
  export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
42
46
  export type { FeedChannel, FeedItem } from './delivery/feeds.js';
@@ -45,6 +49,8 @@ export type { SitemapUrl } from './delivery/sitemap.js';
45
49
  export { buildRobots } from './delivery/robots.js';
46
50
  export { buildSeoMeta } from './delivery/seo.js';
47
51
  export type { SeoInput, SeoMeta } from './delivery/seo.js';
52
+ export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
53
+ export type { SeoFields } from './delivery/seo-fields.js';
48
54
  export { paginate } from './delivery/paginate.js';
49
55
  export type { Page } from './delivery/paginate.js';
50
56
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ export { buildMagicLinkMessage, cloudflareSend } from './email.js';
5
5
  export { CONCEPT_ROUTING, normalizeConcepts, findConcept } from './content/concepts.js';
6
6
  export { composeRuntime } from './content/compose.js';
7
7
  export { frontmatterFromForm, dateInputValue, serializeMarkdown, parseMarkdown, } from './content/frontmatter.js';
8
- export { validateFields } from './content/validate.js';
8
+ export { defineFields } from './content/schema.js';
9
+ export { defineAdapter } from './content/adapter.js';
9
10
  export { isValidId, idFromFilename, filenameFromId, slugify, slugFromId, composeDatedId, } from './content/ids.js';
10
11
  // Render engine (Plan 04): generic directive pipeline; sites own the component registry.
11
12
  export { defineRegistry, emptyValues } from './render/registry.js';
@@ -15,7 +16,7 @@ export { buildComponentInsert } from './render/component-insert.js';
15
16
  export { generateComponentReference } from './render/component-reference.js';
16
17
  export { glyph } from './render/glyph.js';
17
18
  export { remarkDirectiveStamp } from './render/remark-directives.js';
18
- export { rehypeDispatch, isElement, strProp, iconSpan, splitHead, cardShell, markFirstList, } from './render/rehype-dispatch.js';
19
+ export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, markFirstList, } from './render/rehype-dispatch.js';
19
20
  export { createRenderer } from './render/pipeline.js';
20
21
  export { CommitConflictError } from './github/types.js';
21
22
  export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
@@ -29,9 +30,11 @@ export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree,
29
30
  export { permalink } from './content/permalink.js';
30
31
  export { createContentIndex, fromGlob } from './delivery/content-index.js';
31
32
  export { createSiteIndex } from './delivery/site-index.js';
33
+ export { createSiteIndexes } from './delivery/site-indexes.js';
32
34
  export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
33
35
  export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
34
36
  export { buildSitemap } from './delivery/sitemap.js';
35
37
  export { buildRobots } from './delivery/robots.js';
36
38
  export { buildSeoMeta } from './delivery/seo.js';
39
+ export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
37
40
  export { paginate } from './delivery/paginate.js';
@@ -7,4 +7,11 @@ export declare function parseComponent(markdown: string, def: ComponentDef): Pro
7
7
  /** The raw attribute keys present on the component's opening directive, read from the parsed tree
8
8
  * (quote-aware, unlike a regex over the source). Used by validation to flag unknown keys. */
9
9
  export declare function parseRawAttributeKeys(markdown: string, def: ComponentDef): string[];
10
+ /** Parse the component once and derive both the guided-form values and the raw attribute keys.
11
+ * Validation needs both, so this seam spares it the double parse that calling
12
+ * {@link parseComponent} and {@link parseRawAttributeKeys} separately would cost. */
13
+ export declare function parseComponentWithRawKeys(markdown: string, def: ComponentDef): Promise<{
14
+ values: ComponentValues;
15
+ rawKeys: string[];
16
+ }>;
10
17
  //# sourceMappingURL=component-grammar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"component-grammar.d.ts","sourceRoot":"","sources":["../../src/lib/render/component-grammar.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;AA8B5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,GAAG,MAAM,CA2BrF;AAwBD;;wCAEwC;AACxC,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAqClG;AAED;8FAC8F;AAC9F,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,EAAE,CAMnF"}
1
+ {"version":3,"file":"component-grammar.d.ts","sourceRoot":"","sources":["../../src/lib/render/component-grammar.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAW,MAAM,eAAe,CAAC;AA8B5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,GAAG,MAAM,CA2BrF;AA4ED;;wCAEwC;AACxC,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAElG;AAED;8FAC8F;AAC9F,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,EAAE,CAEnF;AAED;;sFAEsF;AACtF,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAGzD"}
@@ -64,12 +64,16 @@ function childrenToText(children) {
64
64
  const root = { type: 'root', children };
65
65
  return String(toMd.stringify(root)).trim();
66
66
  }
67
- /** Parse a serialized component directive back into guided-form values, the inverse of
68
- * {@link serializeComponent}. The grammar is reversible, so the editor can round-trip a
69
- * saved directive through the form. */
70
- export async function parseComponent(markdown, def) {
67
+ // Parse the markdown and find the component's opening container directive. The single seam both
68
+ // parseComponent and parseRawAttributeKeys (and the combined validator helper) build on, so one
69
+ // parse derives both the form values and the raw attribute keys.
70
+ function findComponentRoot(markdown, def) {
71
71
  const tree = unified().use(remarkParse).use(remarkDirective).parse(markdown);
72
- const root = tree.children.find((c) => isContainer(c) && c.name === def.name);
72
+ return tree.children.find((c) => isContainer(c) && c.name === def.name);
73
+ }
74
+ // Build guided-form values from an already-found component root. Returns the empty base when the
75
+ // root is absent.
76
+ function valuesFromRoot(root, def) {
73
77
  const values = emptyComponentValues(def);
74
78
  if (!root)
75
79
  return values;
@@ -101,12 +105,27 @@ export async function parseComponent(markdown, def) {
101
105
  }
102
106
  return values;
103
107
  }
108
+ // The raw attribute keys on an already-found component root.
109
+ function rawKeysFromRoot(root) {
110
+ return Object.keys(root?.attributes ?? {});
111
+ }
112
+ /** Parse a serialized component directive back into guided-form values, the inverse of
113
+ * {@link serializeComponent}. The grammar is reversible, so the editor can round-trip a
114
+ * saved directive through the form. */
115
+ export async function parseComponent(markdown, def) {
116
+ return valuesFromRoot(findComponentRoot(markdown, def), def);
117
+ }
104
118
  /** The raw attribute keys present on the component's opening directive, read from the parsed tree
105
119
  * (quote-aware, unlike a regex over the source). Used by validation to flag unknown keys. */
106
120
  export function parseRawAttributeKeys(markdown, def) {
107
- const tree = unified().use(remarkParse).use(remarkDirective).parse(markdown);
108
- const root = tree.children.find((c) => isContainer(c) && c.name === def.name);
109
- return Object.keys(root?.attributes ?? {});
121
+ return rawKeysFromRoot(findComponentRoot(markdown, def));
122
+ }
123
+ /** Parse the component once and derive both the guided-form values and the raw attribute keys.
124
+ * Validation needs both, so this seam spares it the double parse that calling
125
+ * {@link parseComponent} and {@link parseRawAttributeKeys} separately would cost. */
126
+ export async function parseComponentWithRawKeys(markdown, def) {
127
+ const root = findComponentRoot(markdown, def);
128
+ return { values: valuesFromRoot(root, def), rawKeys: rawKeysFromRoot(root) };
110
129
  }
111
130
  // A bare parse base: empty strings, false, and empty lists, with no attribute defaults applied. The
112
131
  // `emptyValues` helper in registry.ts seeds form defaults instead, so it is deliberately not reused
@@ -1,6 +1,6 @@
1
- import { parseComponent, parseRawAttributeKeys } from './component-grammar.js';
1
+ import { parseComponentWithRawKeys } from './component-grammar.js';
2
2
  export async function validateComponent(markdown, def) {
3
- const values = await parseComponent(markdown, def);
3
+ const { values, rawKeys } = await parseComponentWithRawKeys(markdown, def);
4
4
  const errors = {};
5
5
  const declared = new Set((def.attributes ?? []).map((f) => f.key));
6
6
  for (const field of def.attributes ?? []) {
@@ -14,7 +14,7 @@ export async function validateComponent(markdown, def) {
14
14
  errors[field.key] = `${field.label} must be one of: ${(field.options ?? []).join(', ')}.`;
15
15
  }
16
16
  }
17
- for (const key of parseRawAttributeKeys(markdown, def)) {
17
+ for (const key of rawKeys) {
18
18
  if (!declared.has(key))
19
19
  errors[key] = `Unknown attribute "${key}".`;
20
20
  }
@@ -1,6 +1,9 @@
1
1
  import type { Element } from 'hast';
2
2
  /** A glyph name to SVG path-data map (the site owns the icon set). */
3
3
  export type IconSet = Record<string, string>;
4
- /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill. */
4
+ /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill.
5
+ * An unknown icon name yields the bare svg shell with no path child, so it never serializes
6
+ * a stray empty (or undefined) path. Callers always wrap the returned element, so the shell
7
+ * keeps them safe. */
5
8
  export declare function glyph(name: string, icons: IconSet): Element;
6
9
  //# sourceMappingURL=glyph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["../../src/lib/render/glyph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7C,4FAA4F;AAC5F,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAM3D"}
1
+ {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["../../src/lib/render/glyph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7C;;;uBAGuB;AACvB,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAO3D"}
@@ -1,5 +1,9 @@
1
1
  import { s } from 'hastscript';
2
- /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill. */
2
+ /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill.
3
+ * An unknown icon name yields the bare svg shell with no path child, so it never serializes
4
+ * a stray empty (or undefined) path. Callers always wrap the returned element, so the shell
5
+ * keeps them safe. */
3
6
  export function glyph(name, icons) {
4
- return s('svg', { className: ['ec-glyph'], viewBox: '0 0 256 256', fill: 'currentColor', ariaHidden: 'true' }, [s('path', { d: icons[name] })]);
7
+ const d = icons[name];
8
+ return s('svg', { className: ['ec-glyph'], viewBox: '0 0 256 256', fill: 'currentColor', ariaHidden: 'true' }, d == null ? [] : [s('path', { d })]);
5
9
  }
@@ -1,4 +1,4 @@
1
- import type { Element } from 'hast';
1
+ import type { Element, ElementContent } from 'hast';
2
2
  /** The input types a component attribute or repeatable item field can take. */
3
3
  export type FieldType = 'text' | 'select' | 'icon' | 'boolean';
4
4
  /** One `{key="value"}` attribute on a component directive, or one field of a repeatable item. */
@@ -28,6 +28,20 @@ export interface SlotDef {
28
28
  /** For `kind: 'repeatable'`: the fields composing each list item (v1 uses the first field). */
29
29
  itemFields?: AttributeField[];
30
30
  }
31
+ /** The structured input a component's `build` receives. The engine stamps the component's
32
+ * attributes and partitions its slots from the rendered hast, so `build` arranges hast and
33
+ * never walks the tree. `slot(name)` returns a slot's rendered children (title, body, or any
34
+ * named slot); `items(name)` returns a repeatable slot's items, one child list per item. */
35
+ export interface ComponentContext {
36
+ /** Declared attribute values, keyed by attribute key. Booleans are real booleans. */
37
+ attributes: Record<string, string | boolean>;
38
+ /** A named slot's rendered children. Returns `[]` for an absent or empty slot. */
39
+ slot(name: string): ElementContent[];
40
+ /** A repeatable slot's items, each item its own list of rendered children. `[]` when absent. */
41
+ items(name: string): ElementContent[][];
42
+ /** The stamped component element, for an escape hatch. Most builds never need it. */
43
+ node: Element;
44
+ }
31
45
  /** A site component: how it inserts (editor) and how it renders (rehype). */
32
46
  export interface ComponentDef {
33
47
  /** Directive name, e.g. 'card' (matches `:::card`). */
@@ -38,10 +52,10 @@ export interface ComponentDef {
38
52
  description: string;
39
53
  /** Markdown scaffold inserted at the cursor by the editor palette. */
40
54
  insertTemplate?: string;
41
- /** Build the final hast element from the stamped directive element. The engine
42
- * stamps the entrance-stagger ordinal (`data-rise`) on the top-level result, so a
43
- * build fn stays free of any motion concern. */
44
- build: (node: Element) => Element;
55
+ /** Build the final hast element from the component context (attributes plus partitioned
56
+ * slots). The engine stamps the entrance-stagger ordinal (`data-rise`) on the top-level
57
+ * result, so a build fn stays free of any motion concern. */
58
+ build: (ctx: ComponentContext) => Element;
45
59
  /** Optional role-to-default-icon, e.g. `{ caution: 'warning' }`. */
46
60
  defaultIconByRole?: Record<string, string>;
47
61
  /** One line on when to reach for this component; feeds the picker and the reference file. */
@@ -57,6 +71,10 @@ export interface ComponentRegistry {
57
71
  get(name: string): ComponentDef | undefined;
58
72
  defaultIcon(name: string, role?: string): string | undefined;
59
73
  }
74
+ /** The hast property name carrying one declared attribute from stamp to dispatch, e.g. `tone`
75
+ * becomes `dataAttrTone`. The directive stamp writes it and the rehype dispatch reads it, so both
76
+ * sides derive the name from this one helper rather than spelling the capitalize twice. */
77
+ export declare function dataAttrProp(key: string): string;
60
78
  /**
61
79
  * Build a registry from a site's component definitions. The single source the render
62
80
  * pipeline (directive stamp plus rehype dispatch) and the editor palette both read.
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE/D,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE5D;4GAC4G;AAC5G,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;qDAEiD;IACjD,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IAClC,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,6FAA6F;IAC7F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC5C,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC9D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG,iBAAiB,CAQhG;AAED;uEACuE;AACvE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC1C;AAED;+DAC+D;AAC/D,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,eAAe,CAU9D"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,9 @@
1
+ /** The hast property name carrying one declared attribute from stamp to dispatch, e.g. `tone`
2
+ * becomes `dataAttrTone`. The directive stamp writes it and the rehype dispatch reads it, so both
3
+ * sides derive the name from this one helper rather than spelling the capitalize twice. */
4
+ export function dataAttrProp(key) {
5
+ return `dataAttr${key.charAt(0).toUpperCase()}${key.slice(1)}`;
6
+ }
1
7
  /**
2
8
  * Build a registry from a site's component definitions. The single source the render
3
9
  * pipeline (directive stamp plus rehype dispatch) and the editor palette both read.
@@ -1,15 +1,11 @@
1
1
  import type { Root, Element, ElementContent } from 'hast';
2
- import type { ComponentRegistry } from './registry.js';
2
+ import { type ComponentRegistry } from './registry.js';
3
3
  export declare function isElement(node: ElementContent | undefined): node is Element;
4
4
  export declare function strProp(node: Element, name: string): string | undefined;
5
5
  /** Wrap a pre-built glyph in an ec-icon span; secondary role adds the modifier. */
6
6
  export declare function iconSpan(glyphEl: Element, role?: string): Element;
7
7
  /** A site's icon factory: turn a stamped icon name + role into a hast element. */
8
8
  export type MakeIcon = (name: string, role?: string) => Element;
9
- export declare function splitHead(node: Element, makeIcon?: MakeIcon): {
10
- head: Element;
11
- rest: ElementContent[];
12
- };
13
9
  /** Section wrapper: `<section class=…><div class="card-body">…</div></section>`. */
14
10
  export declare function cardShell(classes: string[], body: ElementContent[]): Element;
15
11
  /** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
@@ -1 +1 @@
1
- {"version":3,"file":"rehype-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/render/rehype-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAE1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,IAAI,OAAO,CAE3E;AAKD,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGvE;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;AAMhE,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,EAAE,CAAA;CAAE,CAYvG;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,OAAO,CAE5E;AAED;kFACkF;AAClF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,OAAO,GAAG,SAAS,CAS7E;AAoBD;;;;;mFAKmF;AACnF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,OAAO,IACnE,MAAM,IAAI,UAYnB"}
1
+ {"version":3,"file":"rehype-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/render/rehype-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAE1D,OAAO,EAA0D,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE/G,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,IAAI,OAAO,CAE3E;AAKD,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGvE;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;AAEhE,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,OAAO,CAE5E;AAED;kFACkF;AAClF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,OAAO,GAAG,SAAS,CAS7E;AAqFD;;;;;mFAKmF;AACnF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,OAAO,IACnE,MAAM,IAAI,UAYnB"}