@glw907/cairn-cms 0.26.0 → 0.33.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 (234) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/dist/auth/crypto.d.ts +0 -1
  3. package/dist/auth/store.d.ts +0 -1
  4. package/dist/auth/types.d.ts +0 -1
  5. package/dist/components/AdminLayout.svelte +372 -44
  6. package/dist/components/AdminLayout.svelte.d.ts +5 -5
  7. package/dist/components/CairnLogo.svelte +28 -0
  8. package/dist/components/CairnLogo.svelte.d.ts +15 -0
  9. package/dist/components/ComponentForm.svelte +1 -1
  10. package/dist/components/ComponentForm.svelte.d.ts +0 -1
  11. package/dist/components/ComponentInsertDialog.svelte.d.ts +0 -1
  12. package/dist/components/ConceptList.svelte +240 -45
  13. package/dist/components/ConceptList.svelte.d.ts +12 -3
  14. package/dist/components/ConfirmPage.svelte +20 -3
  15. package/dist/components/ConfirmPage.svelte.d.ts +0 -1
  16. package/dist/components/DeleteDialog.svelte.d.ts +0 -1
  17. package/dist/components/EditPage.svelte +12 -7
  18. package/dist/components/EditPage.svelte.d.ts +0 -1
  19. package/dist/components/EditorToolbar.svelte.d.ts +0 -1
  20. package/dist/components/IconPicker.svelte.d.ts +0 -1
  21. package/dist/components/LinkPicker.svelte.d.ts +0 -1
  22. package/dist/components/LoginPage.svelte +27 -5
  23. package/dist/components/LoginPage.svelte.d.ts +0 -1
  24. package/dist/components/ManageEditors.svelte +8 -5
  25. package/dist/components/ManageEditors.svelte.d.ts +0 -1
  26. package/dist/components/MarkdownEditor.svelte.d.ts +0 -1
  27. package/dist/components/NavTree.svelte +2 -2
  28. package/dist/components/NavTree.svelte.d.ts +0 -1
  29. package/dist/components/RenameDialog.svelte.d.ts +0 -1
  30. package/dist/components/admin-icons.d.ts +13 -0
  31. package/dist/components/admin-icons.js +15 -0
  32. package/dist/components/cairn-admin.css +5516 -37
  33. package/dist/components/cairn-favicon.d.ts +2 -0
  34. package/dist/components/cairn-favicon.js +7 -0
  35. package/dist/components/chrome-guard.d.ts +9 -0
  36. package/dist/components/chrome-guard.js +55 -0
  37. package/dist/components/fonts/BricolageGrotesque-OFL.txt +93 -0
  38. package/dist/components/fonts/Figtree-OFL.txt +93 -0
  39. package/dist/components/fonts/bricolage-grotesque.woff2 +0 -0
  40. package/dist/components/fonts/figtree.woff2 +0 -0
  41. package/dist/components/index.d.ts +0 -1
  42. package/dist/components/link-completion.d.ts +0 -1
  43. package/dist/components/markdown-format.d.ts +0 -1
  44. package/dist/content/adapter.d.ts +0 -1
  45. package/dist/content/compose.d.ts +1 -2
  46. package/dist/content/compose.js +2 -3
  47. package/dist/content/concepts.d.ts +7 -1
  48. package/dist/content/concepts.js +49 -1
  49. package/dist/content/frontmatter.d.ts +0 -1
  50. package/dist/content/identity.d.ts +23 -0
  51. package/dist/content/identity.js +43 -0
  52. package/dist/content/ids.d.ts +0 -1
  53. package/dist/content/links.d.ts +0 -1
  54. package/dist/content/manifest.d.ts +3 -2
  55. package/dist/content/manifest.js +6 -26
  56. package/dist/content/permalink.d.ts +0 -1
  57. package/dist/content/schema.d.ts +0 -1
  58. package/dist/content/types.d.ts +0 -1
  59. package/dist/content/validate.d.ts +0 -1
  60. package/dist/delivery/CairnHead.svelte.d.ts +0 -1
  61. package/dist/delivery/content-index.d.ts +0 -1
  62. package/dist/delivery/content-index.js +8 -25
  63. package/dist/delivery/data.d.ts +0 -1
  64. package/dist/delivery/excerpt.d.ts +0 -1
  65. package/dist/delivery/feeds.d.ts +0 -1
  66. package/dist/delivery/head.d.ts +0 -1
  67. package/dist/delivery/index.d.ts +0 -1
  68. package/dist/delivery/json-ld.d.ts +0 -1
  69. package/dist/delivery/manifest.d.ts +0 -1
  70. package/dist/delivery/paginate.d.ts +0 -1
  71. package/dist/delivery/responses.d.ts +0 -1
  72. package/dist/delivery/robots.d.ts +0 -1
  73. package/dist/delivery/seo-fields.d.ts +0 -1
  74. package/dist/delivery/seo.d.ts +0 -1
  75. package/dist/delivery/site-descriptors.d.ts +0 -1
  76. package/dist/delivery/site-descriptors.js +5 -6
  77. package/dist/delivery/site-index.d.ts +0 -1
  78. package/dist/delivery/site-indexes.d.ts +0 -1
  79. package/dist/delivery/sitemap.d.ts +0 -1
  80. package/dist/email.d.ts +0 -1
  81. package/dist/env.d.ts +0 -1
  82. package/dist/github/credentials.d.ts +0 -1
  83. package/dist/github/repo.d.ts +0 -1
  84. package/dist/github/signing.d.ts +0 -1
  85. package/dist/github/types.d.ts +0 -1
  86. package/dist/index.d.ts +0 -29
  87. package/dist/index.js +4 -23
  88. package/dist/nav/site-config.d.ts +0 -1
  89. package/dist/render/authoring.d.ts +3 -0
  90. package/dist/render/authoring.js +5 -0
  91. package/dist/render/component-grammar.d.ts +0 -1
  92. package/dist/render/component-insert.d.ts +0 -1
  93. package/dist/render/component-reference.d.ts +0 -1
  94. package/dist/render/component-validate.d.ts +0 -1
  95. package/dist/render/glyph.d.ts +0 -1
  96. package/dist/render/index.d.ts +0 -1
  97. package/dist/render/pipeline.d.ts +0 -1
  98. package/dist/render/pipeline.js +5 -1
  99. package/dist/render/registry.d.ts +2 -1
  100. package/dist/render/registry.js +15 -0
  101. package/dist/render/rehype-dispatch.d.ts +9 -7
  102. package/dist/render/rehype-dispatch.js +12 -6
  103. package/dist/render/remark-directives.d.ts +0 -1
  104. package/dist/render/remark-directives.js +1 -1
  105. package/dist/render/resolve-links.d.ts +0 -1
  106. package/dist/render/sanitize-schema.d.ts +14 -1
  107. package/dist/render/sanitize-schema.js +96 -0
  108. package/dist/sveltekit/auth-routes.d.ts +0 -1
  109. package/dist/sveltekit/content-routes.d.ts +12 -2
  110. package/dist/sveltekit/content-routes.js +37 -13
  111. package/dist/sveltekit/editors-routes.d.ts +0 -1
  112. package/dist/sveltekit/guard.d.ts +0 -1
  113. package/dist/sveltekit/health.d.ts +0 -1
  114. package/dist/sveltekit/index.d.ts +1 -3
  115. package/dist/sveltekit/index.js +0 -1
  116. package/dist/sveltekit/nav-routes.d.ts +0 -1
  117. package/dist/sveltekit/public-routes.d.ts +0 -1
  118. package/dist/sveltekit/types.d.ts +0 -1
  119. package/dist/vite/bin.d.ts +0 -1
  120. package/dist/vite/index.d.ts +0 -1
  121. package/package.json +16 -2
  122. package/src/lib/components/AdminLayout.svelte +372 -44
  123. package/src/lib/components/CairnLogo.svelte +28 -0
  124. package/src/lib/components/ComponentForm.svelte +1 -1
  125. package/src/lib/components/ConceptList.svelte +240 -45
  126. package/src/lib/components/ConfirmPage.svelte +20 -3
  127. package/src/lib/components/EditPage.svelte +12 -7
  128. package/src/lib/components/LoginPage.svelte +27 -5
  129. package/src/lib/components/ManageEditors.svelte +8 -5
  130. package/src/lib/components/NavTree.svelte +2 -2
  131. package/src/lib/components/admin-icons.ts +15 -0
  132. package/src/lib/components/cairn-admin.css +162 -7
  133. package/src/lib/components/cairn-favicon.ts +9 -0
  134. package/src/lib/components/chrome-guard.ts +62 -0
  135. package/src/lib/components/fonts/BricolageGrotesque-OFL.txt +93 -0
  136. package/src/lib/components/fonts/Figtree-OFL.txt +93 -0
  137. package/src/lib/components/fonts/bricolage-grotesque.woff2 +0 -0
  138. package/src/lib/components/fonts/figtree.woff2 +0 -0
  139. package/src/lib/content/compose.ts +3 -3
  140. package/src/lib/content/concepts.ts +61 -1
  141. package/src/lib/content/identity.ts +60 -0
  142. package/src/lib/content/manifest.ts +6 -27
  143. package/src/lib/delivery/content-index.ts +8 -27
  144. package/src/lib/delivery/site-descriptors.ts +5 -6
  145. package/src/lib/index.ts +4 -57
  146. package/src/lib/render/authoring.ts +7 -0
  147. package/src/lib/render/pipeline.ts +4 -1
  148. package/src/lib/render/registry.ts +20 -0
  149. package/src/lib/render/rehype-dispatch.ts +13 -6
  150. package/src/lib/render/remark-directives.ts +1 -1
  151. package/src/lib/render/sanitize-schema.ts +97 -0
  152. package/src/lib/sveltekit/content-routes.ts +51 -14
  153. package/src/lib/sveltekit/index.ts +2 -8
  154. package/dist/auth/crypto.d.ts.map +0 -1
  155. package/dist/auth/store.d.ts.map +0 -1
  156. package/dist/auth/types.d.ts.map +0 -1
  157. package/dist/components/AdminLayout.svelte.d.ts.map +0 -1
  158. package/dist/components/ComponentForm.svelte.d.ts.map +0 -1
  159. package/dist/components/ComponentInsertDialog.svelte.d.ts.map +0 -1
  160. package/dist/components/ConceptList.svelte.d.ts.map +0 -1
  161. package/dist/components/ConfirmPage.svelte.d.ts.map +0 -1
  162. package/dist/components/DeleteDialog.svelte.d.ts.map +0 -1
  163. package/dist/components/EditPage.svelte.d.ts.map +0 -1
  164. package/dist/components/EditorToolbar.svelte.d.ts.map +0 -1
  165. package/dist/components/IconPicker.svelte.d.ts.map +0 -1
  166. package/dist/components/LinkPicker.svelte.d.ts.map +0 -1
  167. package/dist/components/LoginPage.svelte.d.ts.map +0 -1
  168. package/dist/components/ManageEditors.svelte.d.ts.map +0 -1
  169. package/dist/components/MarkdownEditor.svelte.d.ts.map +0 -1
  170. package/dist/components/NavTree.svelte.d.ts.map +0 -1
  171. package/dist/components/RenameDialog.svelte.d.ts.map +0 -1
  172. package/dist/components/index.d.ts.map +0 -1
  173. package/dist/components/link-completion.d.ts.map +0 -1
  174. package/dist/components/markdown-format.d.ts.map +0 -1
  175. package/dist/content/adapter.d.ts.map +0 -1
  176. package/dist/content/compose.d.ts.map +0 -1
  177. package/dist/content/concepts.d.ts.map +0 -1
  178. package/dist/content/frontmatter.d.ts.map +0 -1
  179. package/dist/content/ids.d.ts.map +0 -1
  180. package/dist/content/links.d.ts.map +0 -1
  181. package/dist/content/manifest.d.ts.map +0 -1
  182. package/dist/content/permalink.d.ts.map +0 -1
  183. package/dist/content/schema.d.ts.map +0 -1
  184. package/dist/content/types.d.ts.map +0 -1
  185. package/dist/content/validate.d.ts.map +0 -1
  186. package/dist/delivery/CairnHead.svelte.d.ts.map +0 -1
  187. package/dist/delivery/content-index.d.ts.map +0 -1
  188. package/dist/delivery/data.d.ts.map +0 -1
  189. package/dist/delivery/excerpt.d.ts.map +0 -1
  190. package/dist/delivery/feeds.d.ts.map +0 -1
  191. package/dist/delivery/head.d.ts.map +0 -1
  192. package/dist/delivery/index.d.ts.map +0 -1
  193. package/dist/delivery/json-ld.d.ts.map +0 -1
  194. package/dist/delivery/manifest.d.ts.map +0 -1
  195. package/dist/delivery/paginate.d.ts.map +0 -1
  196. package/dist/delivery/responses.d.ts.map +0 -1
  197. package/dist/delivery/robots.d.ts.map +0 -1
  198. package/dist/delivery/seo-fields.d.ts.map +0 -1
  199. package/dist/delivery/seo.d.ts.map +0 -1
  200. package/dist/delivery/site-descriptors.d.ts.map +0 -1
  201. package/dist/delivery/site-index.d.ts.map +0 -1
  202. package/dist/delivery/site-indexes.d.ts.map +0 -1
  203. package/dist/delivery/sitemap.d.ts.map +0 -1
  204. package/dist/email.d.ts.map +0 -1
  205. package/dist/env.d.ts.map +0 -1
  206. package/dist/github/credentials.d.ts.map +0 -1
  207. package/dist/github/repo.d.ts.map +0 -1
  208. package/dist/github/signing.d.ts.map +0 -1
  209. package/dist/github/types.d.ts.map +0 -1
  210. package/dist/index.d.ts.map +0 -1
  211. package/dist/nav/site-config.d.ts.map +0 -1
  212. package/dist/render/component-grammar.d.ts.map +0 -1
  213. package/dist/render/component-insert.d.ts.map +0 -1
  214. package/dist/render/component-reference.d.ts.map +0 -1
  215. package/dist/render/component-validate.d.ts.map +0 -1
  216. package/dist/render/glyph.d.ts.map +0 -1
  217. package/dist/render/index.d.ts.map +0 -1
  218. package/dist/render/pipeline.d.ts.map +0 -1
  219. package/dist/render/registry.d.ts.map +0 -1
  220. package/dist/render/rehype-dispatch.d.ts.map +0 -1
  221. package/dist/render/remark-directives.d.ts.map +0 -1
  222. package/dist/render/resolve-links.d.ts.map +0 -1
  223. package/dist/render/sanitize-schema.d.ts.map +0 -1
  224. package/dist/sveltekit/auth-routes.d.ts.map +0 -1
  225. package/dist/sveltekit/content-routes.d.ts.map +0 -1
  226. package/dist/sveltekit/editors-routes.d.ts.map +0 -1
  227. package/dist/sveltekit/guard.d.ts.map +0 -1
  228. package/dist/sveltekit/health.d.ts.map +0 -1
  229. package/dist/sveltekit/index.d.ts.map +0 -1
  230. package/dist/sveltekit/nav-routes.d.ts.map +0 -1
  231. package/dist/sveltekit/public-routes.d.ts.map +0 -1
  232. package/dist/sveltekit/types.d.ts.map +0 -1
  233. package/dist/vite/bin.d.ts.map +0 -1
  234. package/dist/vite/index.d.ts.map +0 -1
@@ -3,47 +3,30 @@
3
3
  // returns cheap plain-data summaries plus an on-demand detail lookup. It is concept-generic:
4
4
  // every operation reads the descriptor and its routing rule, never a hardcoded concept id.
5
5
  import { parseMarkdown } from '../content/frontmatter.js';
6
- import { idFromFilename, slugFromId } from '../content/ids.js';
7
- import { permalink } from '../content/permalink.js';
6
+ import { entryId, entryIdentity, asDate, asString, asTags } from '../content/identity.js';
8
7
  import { deriveExcerpt, wordCount } from './excerpt.js';
9
8
  /** Map a Vite eager `?raw` glob record (`{ path: raw }`) to `RawFile[]`. */
10
9
  export function fromGlob(record) {
11
10
  return Object.entries(record).map(([path, raw]) => ({ path, raw }));
12
11
  }
13
- function basename(path) {
14
- const slash = path.lastIndexOf('/');
15
- return slash >= 0 ? path.slice(slash + 1) : path;
16
- }
17
- function asString(value) {
18
- return typeof value === 'string' && value.trim() ? value : undefined;
19
- }
20
- function asDate(value) {
21
- if (value instanceof Date)
22
- return Number.isNaN(value.getTime()) ? undefined : value.toISOString().slice(0, 10);
23
- if (typeof value === 'string')
24
- return value.match(/^\d{4}-\d{2}-\d{2}/)?.[0];
25
- return undefined;
26
- }
27
- function asTags(value) {
28
- return Array.isArray(value) ? value.map(String) : [];
29
- }
30
12
  /** Build a concept's index from its raw files and normalized descriptor. */
31
13
  export function createContentIndex(files, descriptor) {
32
14
  const problems = [];
33
15
  const entries = [];
34
16
  for (const file of files) {
35
- const id = idFromFilename(basename(file.path));
36
- const slug = slugFromId(id, descriptor.routing.dated ? descriptor.datePrefix : null);
37
17
  const { frontmatter: raw, body } = parseMarkdown(file.raw);
38
- const date = asDate(raw.date);
18
+ const id = entryId(file.path);
39
19
  const draft = raw.draft === true;
40
- // Validate once at build. A failure is recorded for the site gate and excluded from the typed
41
- // read, so every readable entry's frontmatter is the validator's normalized output, never raw.
20
+ // Validate before resolving the permalink. A date-token permalink throws on an entry with no
21
+ // valid date; the validate gate records that as a content problem rather than aborting the whole
22
+ // index build, so one bad entry degrades to a skip, not a crash. A failure is also excluded from
23
+ // the typed read, so every readable entry's frontmatter is the validator's normalized output.
42
24
  const result = descriptor.validate(raw, body);
43
25
  if (!result.ok) {
44
26
  problems.push({ id, draft, errors: result.errors });
45
27
  continue;
46
28
  }
29
+ const { slug, date, permalink } = entryIdentity(descriptor, file.path, raw);
47
30
  const summaryFieldValues = {};
48
31
  for (const key of descriptor.summaryFields) {
49
32
  if (key in result.data)
@@ -53,7 +36,7 @@ export function createContentIndex(files, descriptor) {
53
36
  concept: descriptor.id,
54
37
  id,
55
38
  slug,
56
- permalink: permalink(descriptor, { id, slug, date }),
39
+ permalink,
57
40
  title: asString(raw.title) ?? id,
58
41
  date,
59
42
  updated: asDate(raw.updated),
@@ -21,4 +21,3 @@ export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from '
21
21
  export { jsonLdScript } from './json-ld.js';
22
22
  export { permalink } from '../content/permalink.js';
23
23
  export { buildSiteManifest, buildLinkResolver } from './manifest.js';
24
- //# sourceMappingURL=data.d.ts.map
@@ -8,4 +8,3 @@ export declare function deriveExcerpt(body: string, opts?: {
8
8
  }): string;
9
9
  /** Count words in the stripped body. */
10
10
  export declare function wordCount(body: string): number;
11
- //# sourceMappingURL=excerpt.d.ts.map
@@ -24,4 +24,3 @@ export interface FeedItem {
24
24
  export declare function buildRssFeed(channel: FeedChannel, items: FeedItem[]): string;
25
25
  /** Build a JSON Feed 1.1 document. */
26
26
  export declare function buildJsonFeed(channel: FeedChannel, items: FeedItem[]): string;
27
- //# sourceMappingURL=feeds.d.ts.map
@@ -1,2 +1 @@
1
1
  export { default as CairnHead } from './CairnHead.svelte';
2
- //# sourceMappingURL=head.d.ts.map
@@ -1,4 +1,3 @@
1
1
  export * from './data.js';
2
2
  export { createPublicRoutes } from '../sveltekit/public-routes.js';
3
3
  export type { PublicRoutesDeps, ListData, TagData, TagIndexData, EntryData, } from '../sveltekit/public-routes.js';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1 @@
1
1
  export declare function jsonLdScript(data: Record<string, unknown>): string;
2
- //# sourceMappingURL=json-ld.d.ts.map
@@ -10,4 +10,3 @@ export declare function buildSiteManifest<A extends CairnAdapter>(adapter: A, co
10
10
  /** A resolver backed by the site index, for the build. A miss throws, so a dangling cairn: token
11
11
  * fails the prerender (the build backstop). The preview uses manifestLinkResolver, which marks. */
12
12
  export declare function buildLinkResolver(site: SiteIndex): LinkResolve;
13
- //# sourceMappingURL=manifest.d.ts.map
@@ -10,4 +10,3 @@ export interface Page<T> {
10
10
  }
11
11
  /** Slice `items` into the 1-based `page` of size `perPage`, clamping the page into bounds. */
12
12
  export declare function paginate<T>(items: T[], page: number, perPage: number): Page<T>;
13
- //# sourceMappingURL=paginate.d.ts.map
@@ -11,4 +11,3 @@ export declare function robotsResponse(opts: {
11
11
  sitemapUrl: string;
12
12
  disallow?: string[];
13
13
  }): Response;
14
- //# sourceMappingURL=responses.d.ts.map
@@ -3,4 +3,3 @@ export declare function buildRobots(opts: {
3
3
  sitemapUrl: string;
4
4
  disallow?: string[];
5
5
  }): string;
6
- //# sourceMappingURL=robots.d.ts.map
@@ -19,4 +19,3 @@ export declare function readSeoFields(frontmatter: Record<string, unknown>): Seo
19
19
  * bare path also anchors to the origin root; against a sub-path origin it would resolve relative to
20
20
  * that path, per the WHATWG URL rules. */
21
21
  export declare function resolveImageUrl(image: string, origin: string): string | undefined;
22
- //# sourceMappingURL=seo-fields.d.ts.map
@@ -35,4 +35,3 @@ export interface SeoMeta {
35
35
  }
36
36
  /** Build the head data for a page. */
37
37
  export declare function buildSeoMeta(input: SeoInput): SeoMeta;
38
- //# sourceMappingURL=seo.d.ts.map
@@ -2,4 +2,3 @@ import type { CairnAdapter, ConceptDescriptor } from '../content/types.js';
2
2
  import type { SiteConfig } from '../nav/site-config.js';
3
3
  /** Per-concept descriptors for a site, from its adapter content and its parsed site config. */
4
4
  export declare function siteDescriptors(adapter: CairnAdapter, siteConfig: SiteConfig): ConceptDescriptor[];
5
- //# sourceMappingURL=site-descriptors.d.ts.map
@@ -1,9 +1,8 @@
1
- // cairn-cms: the one-call descriptor helper. A delivery site needs the same per-concept
2
- // descriptors the admin runtime uses; this wraps the two calls that derive them so the
3
- // pairing is not tribal knowledge. The YAML URL policy stays the single source of truth.
4
- import { normalizeConcepts } from '../content/concepts.js';
5
- import { urlPolicyFrom } from '../nav/site-config.js';
1
+ // cairn-cms: the one-call descriptor helper. A delivery site needs the same per-concept descriptors
2
+ // the admin runtime uses; this delegates to the shared resolveConcepts so the pairing is one path, not
3
+ // tribal knowledge. The YAML URL policy stays the single source of truth.
4
+ import { resolveConcepts } from '../content/concepts.js';
6
5
  /** Per-concept descriptors for a site, from its adapter content and its parsed site config. */
7
6
  export function siteDescriptors(adapter, siteConfig) {
8
- return normalizeConcepts(adapter.content, urlPolicyFrom(siteConfig));
7
+ return resolveConcepts(adapter.content, siteConfig);
9
8
  }
@@ -31,4 +31,3 @@ export interface SiteIndex {
31
31
  export declare function createSiteIndex(concepts: ConceptIndex[], opts?: {
32
32
  validate?: boolean;
33
33
  }): SiteIndex;
34
- //# sourceMappingURL=site-index.d.ts.map
@@ -23,4 +23,3 @@ export type SiteIndexes<A extends CairnAdapter> = {
23
23
  export declare function createSiteIndexes<const A extends CairnAdapter>(adapter: A, config: SiteConfig, globs: SiteGlobs<A>, opts?: {
24
24
  validate?: boolean;
25
25
  }): SiteIndexes<A>;
26
- //# sourceMappingURL=site-indexes.d.ts.map
@@ -5,4 +5,3 @@ export interface SitemapUrl {
5
5
  }
6
6
  /** Build a sitemap XML document from a list of URLs. */
7
7
  export declare function buildSitemap(urls: SitemapUrl[]): string;
8
- //# sourceMappingURL=sitemap.d.ts.map
package/dist/email.d.ts CHANGED
@@ -24,4 +24,3 @@ export declare function buildMagicLinkMessage(input: {
24
24
  }): MagicLinkMessage;
25
25
  /** The production send: Cloudflare Email Sending through the EMAIL binding. */
26
26
  export declare const cloudflareSend: SendMagicLink;
27
- //# sourceMappingURL=email.d.ts.map
package/dist/env.d.ts CHANGED
@@ -21,4 +21,3 @@ export declare function requireOrigin(env: {
21
21
  export declare function requireDb(env: {
22
22
  AUTH_DB?: D1Database;
23
23
  }): D1Database;
24
- //# sourceMappingURL=env.d.ts.map
@@ -9,4 +9,3 @@ export interface GithubKeyEnv {
9
9
  * installation) and the Worker's private-key secret. Throws when the secret is unset.
10
10
  */
11
11
  export declare function appCredentials(backend: Pick<BackendConfig, 'appId' | 'installationId'>, env: GithubKeyEnv): AppCredentials;
12
- //# sourceMappingURL=credentials.d.ts.map
@@ -67,4 +67,3 @@ export declare function commitFiles(repo: RepoRef, changes: FileChange[], opts:
67
67
  author: CommitAuthor;
68
68
  }, token: string): Promise<string>;
69
69
  export {};
70
- //# sourceMappingURL=repo.d.ts.map
@@ -26,4 +26,3 @@ export declare function signingSelfTest(appId: string, privateKeyB64: string): P
26
26
  ok: boolean;
27
27
  detail?: string;
28
28
  }>;
29
- //# sourceMappingURL=signing.d.ts.map
@@ -32,4 +32,3 @@ export declare class CommitConflictError extends Error {
32
32
  readonly path: string;
33
33
  constructor(path: string);
34
34
  }
35
- //# sourceMappingURL=types.d.ts.map
package/dist/index.d.ts CHANGED
@@ -27,38 +27,9 @@ export type { ReferenceOptions } from './render/component-reference.js';
27
27
  export { glyph } from './render/glyph.js';
28
28
  export type { IconSet } from './render/glyph.js';
29
29
  export { remarkDirectiveStamp } from './render/remark-directives.js';
30
- export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, headRow, markFirstList, } from './render/rehype-dispatch.js';
31
- export type { MakeIcon } from './render/rehype-dispatch.js';
32
30
  export { createRenderer } from './render/pipeline.js';
33
31
  export type { RendererOptions } from './render/pipeline.js';
34
32
  export type { RepoRef, RepoFile, CommitAuthor, AppCredentials } from './github/types.js';
35
33
  export { CommitConflictError } from './github/types.js';
36
- export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
37
- export { treeUrl, markdownFilesIn, listMarkdown, contentsUrl, readRaw, fileSha, commitFile, } from './github/repo.js';
38
- export { appCredentials } from './github/credentials.js';
39
- export type { GithubKeyEnv } from './github/credentials.js';
40
34
  export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
41
35
  export type { NavNode, SiteConfig } from './nav/site-config.js';
42
- export { permalink } from './content/permalink.js';
43
- export { createContentIndex, fromGlob } from './delivery/content-index.js';
44
- export type { RawFile, ContentSummary, ContentEntry, ContentIndex, ContentProblem, } from './delivery/content-index.js';
45
- export { createSiteIndex } from './delivery/site-index.js';
46
- export type { SiteIndex, ConceptIndex } from './delivery/site-index.js';
47
- export { createSiteIndexes } from './delivery/site-indexes.js';
48
- export type { SiteIndexes, SiteGlobs } from './delivery/site-indexes.js';
49
- export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
50
- export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
51
- export type { FeedChannel, FeedItem } from './delivery/feeds.js';
52
- export { buildSitemap } from './delivery/sitemap.js';
53
- export type { SitemapUrl } from './delivery/sitemap.js';
54
- export { buildRobots } from './delivery/robots.js';
55
- export { buildSeoMeta } from './delivery/seo.js';
56
- export type { SeoInput, SeoMeta } from './delivery/seo.js';
57
- export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
58
- export type { SeoFields } from './delivery/seo-fields.js';
59
- export { paginate } from './delivery/paginate.js';
60
- export type { Page } from './delivery/paginate.js';
61
- export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './delivery/responses.js';
62
- export { createPublicRoutes } from './sveltekit/public-routes.js';
63
- export type { PublicRoutesDeps, ListData, TagData, TagIndexData, EntryData } from './sveltekit/public-routes.js';
64
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -21,30 +21,11 @@ export { buildComponentInsert } from './render/component-insert.js';
21
21
  export { generateComponentReference } from './render/component-reference.js';
22
22
  export { glyph } from './render/glyph.js';
23
23
  export { remarkDirectiveStamp } from './render/remark-directives.js';
24
- export { rehypeDispatch, isElement, strProp, iconSpan, cardShell, headRow, markFirstList, } from './render/rehype-dispatch.js';
24
+ // The component-authoring helpers (iconSpan, cardShell, headRow, isElement, strAttr) live on the
25
+ // @glw907/cairn-cms/render subpath, not the root barrel. rehypeDispatch is deliberately not public:
26
+ // createRenderer is the one public render pipeline, so the safe plugin ordering is the only public
27
+ // path. See docs/superpowers/specs/2026-06-05-cairn-render-authoring-surface-design.md.
25
28
  export { createRenderer } from './render/pipeline.js';
26
29
  export { CommitConflictError } from './github/types.js';
27
- export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
28
- export { treeUrl, markdownFilesIn, listMarkdown, contentsUrl, readRaw, fileSha, commitFile, } from './github/repo.js';
29
- export { appCredentials } from './github/credentials.js';
30
30
  // Nav tree and site-config helpers (Plan 06).
31
31
  export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
32
- // Public content delivery (public-delivery design): the query index, syndication, and
33
- // discovery surface that sites read. Pure builders plus the one permalink resolver; the
34
- // SvelteKit loaders live under the /sveltekit subpath.
35
- export { permalink } from './content/permalink.js';
36
- export { createContentIndex, fromGlob } from './delivery/content-index.js';
37
- export { createSiteIndex } from './delivery/site-index.js';
38
- export { createSiteIndexes } from './delivery/site-indexes.js';
39
- export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
40
- export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
41
- export { buildSitemap } from './delivery/sitemap.js';
42
- export { buildRobots } from './delivery/robots.js';
43
- export { buildSeoMeta } from './delivery/seo.js';
44
- export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
45
- export { paginate } from './delivery/paginate.js';
46
- // Root superset of the delivery route surface: a wrong guess from root for a route loader or a
47
- // response helper now resolves. The CairnHead component stays out of root so the root barrel stays
48
- // node-importable for the unit suite; it resolves from @glw907/cairn-cms/delivery/head.
49
- export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './delivery/responses.js';
50
- export { createPublicRoutes } from './sveltekit/public-routes.js';
@@ -52,4 +52,3 @@ export declare function urlPolicyFrom(config: SiteConfig): Record<string, Concep
52
52
  * serializes without `url`/`children` keys.
53
53
  */
54
54
  export declare function setMenu(raw: string, name: string, tree: NavNode[]): string;
55
- //# sourceMappingURL=site-config.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { iconSpan, cardShell, headRow, isElement, strAttr } from './rehype-dispatch.js';
2
+ export type { MakeIcon } from './rehype-dispatch.js';
3
+ export type { ComponentContext } from './registry.js';
@@ -0,0 +1,5 @@
1
+ // cairn-cms: the component-authoring toolkit (@glw907/cairn-cms/render). A site authoring components
2
+ // through build(ctx) reaches for these hast builders and the string-attribute reader. Curated on
3
+ // purpose: the internal hast helpers (strProp, markFirstList, dataAttrProp) stay internal, and
4
+ // rehypeDispatch is deliberately omitted (createRenderer is the one public render pipeline).
5
+ export { iconSpan, cardShell, headRow, isElement, strAttr } from './rehype-dispatch.js';
@@ -14,4 +14,3 @@ export declare function parseComponentWithRawKeys(markdown: string, def: Compone
14
14
  values: ComponentValues;
15
15
  rawKeys: string[];
16
16
  }>;
17
- //# sourceMappingURL=component-grammar.d.ts.map
@@ -11,4 +11,3 @@ export type ComponentInsert = {
11
11
  /** Serialize a component's form values, then validate the result against its schema. Returns the
12
12
  * markdown to insert at the cursor, or the field errors keyed by attribute key or slot name. */
13
13
  export declare function buildComponentInsert(def: ComponentDef, values: ComponentValues): Promise<ComponentInsert>;
14
- //# sourceMappingURL=component-insert.d.ts.map
@@ -8,4 +8,3 @@ export interface ReferenceOptions {
8
8
  /** Build a self-contained markdown reference (the llms-full.txt shape) for a component registry, for
9
9
  * authors and for pointing an LLM at one curated file. */
10
10
  export declare function generateComponentReference(registry: ComponentRegistry, opts: ReferenceOptions): string;
11
- //# sourceMappingURL=component-reference.d.ts.map
@@ -7,4 +7,3 @@ export type ComponentValidation = {
7
7
  errors: Record<string, string>;
8
8
  };
9
9
  export declare function validateComponent(markdown: string, def: ComponentDef): Promise<ComponentValidation>;
10
- //# sourceMappingURL=component-validate.d.ts.map
@@ -6,4 +6,3 @@ export type IconSet = Record<string, string>;
6
6
  * a stray empty (or undefined) path. Callers always wrap the returned element, so the shell
7
7
  * keeps them safe. */
8
8
  export declare function glyph(name: string, icons: IconSet): Element;
9
- //# sourceMappingURL=glyph.d.ts.map
@@ -3,4 +3,3 @@ export * from './glyph.js';
3
3
  export * from './remark-directives.js';
4
4
  export * from './rehype-dispatch.js';
5
5
  export * from './pipeline.js';
6
- //# sourceMappingURL=index.d.ts.map
@@ -31,4 +31,3 @@ export declare function createRenderer(registry?: ComponentRegistry, options?: R
31
31
  resolve?: LinkResolve;
32
32
  }) => Promise<string>;
33
33
  };
34
- //# sourceMappingURL=pipeline.d.ts.map
@@ -8,7 +8,7 @@ import rehypeSlug from 'rehype-slug';
8
8
  import rehypeStringify from 'rehype-stringify';
9
9
  import rehypeSanitize from 'rehype-sanitize';
10
10
  import { VFile } from 'vfile';
11
- import { buildSanitizeSchema, rehypeAnchorRel } from './sanitize-schema.js';
11
+ import { buildSanitizeSchema, rehypeAnchorRel, rehypeSinkGuard } from './sanitize-schema.js';
12
12
  import { remarkDirectiveStamp } from './remark-directives.js';
13
13
  import { remarkResolveCairnLinks, CAIRN_RESOLVE } from './resolve-links.js';
14
14
  import { rehypeDispatch } from './rehype-dispatch.js';
@@ -33,6 +33,10 @@ export function createRenderer(registry = defineRegistry({ components: [] }), op
33
33
  ];
34
34
  if (rel !== false)
35
35
  rehypePlugins.push([rehypeAnchorRel, rel]);
36
+ // The sink guard runs last, over the fully-built tree, so it neutralizes a sink a component
37
+ // build() emitted after the floor. Gated by the same switch as the floor.
38
+ if (!options.unsafeDisableSanitize)
39
+ rehypePlugins.push(rehypeSinkGuard);
36
40
  const processor = unified()
37
41
  .use(remarkParse)
38
42
  .use(remarkGfm)
@@ -70,6 +70,8 @@ export interface ComponentRegistry {
70
70
  names: string[];
71
71
  get(name: string): ComponentDef | undefined;
72
72
  defaultIcon(name: string, role?: string): string | undefined;
73
+ /** The component's first `type:'icon'` attribute, or undefined when it declares none. */
74
+ iconField(name: string): AttributeField | undefined;
73
75
  }
74
76
  /** The hast property name carrying one declared attribute from stamp to dispatch, e.g. `tone`
75
77
  * becomes `dataAttrTone`. The directive stamp writes it and the rehype dispatch reads it, so both
@@ -91,4 +93,3 @@ export interface ComponentValues {
91
93
  /** Seed an empty {@link ComponentValues} from a component's schema: attribute defaults (or '' / false)
92
94
  * and empty slot values ([] for repeatable, '' otherwise). */
93
95
  export declare function emptyValues(def: ComponentDef): ComponentValues;
94
- //# sourceMappingURL=registry.d.ts.map
@@ -4,17 +4,32 @@
4
4
  export function dataAttrProp(key) {
5
5
  return `dataAttr${key.charAt(0).toUpperCase()}${key.slice(1)}`;
6
6
  }
7
+ /** A component's first `type:'icon'` attribute, or undefined when it declares none. Both the
8
+ * construction-time guard and the registry's `iconField` derive the icon field from this one
9
+ * predicate rather than spelling the `type === 'icon'` find twice. */
10
+ function findIconField(def) {
11
+ return def.attributes?.find((field) => field.type === 'icon');
12
+ }
7
13
  /**
8
14
  * Build a registry from a site's component definitions. The single source the render
9
15
  * pipeline (directive stamp plus rehype dispatch) and the editor palette both read.
10
16
  */
11
17
  export function defineRegistry({ components }) {
18
+ for (const c of components) {
19
+ if (c.defaultIconByRole && Object.keys(c.defaultIconByRole).length > 0 && !findIconField(c)) {
20
+ throw new Error(`cairn: component "${c.name}" sets defaultIconByRole but declares no type:'icon' attribute, so the default icon can never render`);
21
+ }
22
+ }
12
23
  const byName = new Map(components.map((c) => [c.name, c]));
13
24
  return {
14
25
  defs: components,
15
26
  names: components.map((c) => c.name),
16
27
  get: (name) => byName.get(name),
17
28
  defaultIcon: (name, role) => (role ? byName.get(name)?.defaultIconByRole?.[role] : undefined),
29
+ iconField: (name) => {
30
+ const def = byName.get(name);
31
+ return def ? findIconField(def) : undefined;
32
+ },
18
33
  };
19
34
  }
20
35
  /** Seed an empty {@link ComponentValues} from a component's schema: attribute defaults (or '' / false)
@@ -1,6 +1,9 @@
1
1
  import type { Root, Element, ElementContent } from 'hast';
2
- import { type ComponentRegistry } from './registry.js';
2
+ import { type ComponentContext, type ComponentRegistry } from './registry.js';
3
3
  export declare function isElement(node: ElementContent | undefined): node is Element;
4
+ /** Read a declared string attribute off the component context, returning undefined for a boolean or
5
+ * absent value. Replaces the `typeof ctx.attributes[key] === 'string'` narrowing a build repeats. */
6
+ export declare function strAttr(ctx: ComponentContext, key: string): string | undefined;
4
7
  export declare function strProp(node: Element, name: string): string | undefined;
5
8
  /** Wrap a pre-built glyph in an ec-icon span; secondary role adds the modifier. */
6
9
  export declare function iconSpan(glyphEl: Element, role?: string): Element;
@@ -8,11 +11,11 @@ export declare function iconSpan(glyphEl: Element, role?: string): Element;
8
11
  export type MakeIcon = (name: string, role?: string) => Element;
9
12
  /** Section wrapper: `<section class=…><div class="card-body">…</div></section>`. */
10
13
  export declare function cardShell(classes: string[], body: ElementContent[]): Element;
11
- /** Card head row: `<div class="ec-head">[icon]<h2 class="card-title">{title}</h2></div>`.
12
- * Pass the title's inline children and an optional pre-built icon element, the way `cardShell`
13
- * takes already-built body content. This factors the icon-plus-heading head that a titled
14
- * component build would otherwise rebuild by hand (the shape the removed `splitHead` produced). */
15
- export declare function headRow(title: ElementContent[], icon?: Element): Element;
14
+ /** Card head row: `<div class="ec-head">[icon]<hN class="card-title">{title}</hN></div>`.
15
+ * Pass the title's inline children, an optional pre-built icon element, and an optional heading
16
+ * level (default 2). This factors the icon-plus-heading head that a titled component build would
17
+ * otherwise rebuild by hand (the shape the removed `splitHead` produced). */
18
+ export declare function headRow(title: ElementContent[], icon?: Element, level?: number): Element;
16
19
  /** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
17
20
  * text nodes so the bare list serializes without newlines. Returns that <ul>. */
18
21
  export declare function markFirstList(children: ElementContent[]): Element | undefined;
@@ -23,4 +26,3 @@ export declare function markFirstList(children: ElementContent[]): Element | und
23
26
  * `data-rise` while dropping `style`. Nested primitives never get it. Non-primitive
24
27
  * content (lede, intro paragraphs, the page-toc nav) passes through untouched. */
25
28
  export declare function rehypeDispatch(registry: ComponentRegistry, stagger?: boolean): (tree: Root) => void;
26
- //# sourceMappingURL=rehype-dispatch.d.ts.map
@@ -3,6 +3,12 @@ import { dataAttrProp } from './registry.js';
3
3
  export function isElement(node) {
4
4
  return !!node && node.type === 'element';
5
5
  }
6
+ /** Read a declared string attribute off the component context, returning undefined for a boolean or
7
+ * absent value. Replaces the `typeof ctx.attributes[key] === 'string'` narrowing a build repeats. */
8
+ export function strAttr(ctx, key) {
9
+ const value = ctx.attributes[key];
10
+ return typeof value === 'string' ? value : undefined;
11
+ }
6
12
  // hast Properties values are PropertyValue (string | number | boolean | array | null).
7
13
  // Directive markers (dataPrimitive/dataRole/dataAttr<Key>) are always stamped as strings;
8
14
  // this reads them back with that guarantee instead of casting at each call site.
@@ -19,15 +25,15 @@ export function iconSpan(glyphEl, role) {
19
25
  export function cardShell(classes, body) {
20
26
  return h('section', { className: classes }, [h('div', { className: ['card-body'] }, body)]);
21
27
  }
22
- /** Card head row: `<div class="ec-head">[icon]<h2 class="card-title">{title}</h2></div>`.
23
- * Pass the title's inline children and an optional pre-built icon element, the way `cardShell`
24
- * takes already-built body content. This factors the icon-plus-heading head that a titled
25
- * component build would otherwise rebuild by hand (the shape the removed `splitHead` produced). */
26
- export function headRow(title, icon) {
28
+ /** Card head row: `<div class="ec-head">[icon]<hN class="card-title">{title}</hN></div>`.
29
+ * Pass the title's inline children, an optional pre-built icon element, and an optional heading
30
+ * level (default 2). This factors the icon-plus-heading head that a titled component build would
31
+ * otherwise rebuild by hand (the shape the removed `splitHead` produced). */
32
+ export function headRow(title, icon, level = 2) {
27
33
  const children = [];
28
34
  if (icon)
29
35
  children.push(icon);
30
- children.push(h('h2', { className: ['card-title'] }, title));
36
+ children.push(h(`h${level}`, { className: ['card-title'] }, title));
31
37
  return h('div', { className: ['ec-head'] }, children);
32
38
  }
33
39
  /** Tag the first <ul> among children with `ec-grid` and strip its whitespace-only
@@ -1,4 +1,3 @@
1
1
  import type { Root } from 'mdast';
2
2
  import { type ComponentRegistry } from './registry.js';
3
3
  export declare function remarkDirectiveStamp(registry: ComponentRegistry): (tree: Root) => void;
4
- //# sourceMappingURL=remark-directives.d.ts.map
@@ -60,7 +60,7 @@ export function remarkDirectiveStamp(registry) {
60
60
  const def = registry.get(node.name);
61
61
  const attrs = node.attributes ?? {};
62
62
  const role = attrs.role || undefined;
63
- const iconField = def?.attributes?.find((field) => field.type === 'icon');
63
+ const iconField = registry.iconField(node.name);
64
64
  const iconKey = iconField?.key ?? 'icon';
65
65
  let icon = attrs[iconKey] || undefined;
66
66
  if (!icon && role)
@@ -5,4 +5,3 @@ export declare const CAIRN_RESOLVE = "cairnResolve";
5
5
  * pass through. A missing target is marked with the cairn-broken-link class (the resolver returns
6
6
  * undefined) or, when the resolver throws, the error propagates and fails the build. */
7
7
  export declare function remarkResolveCairnLinks(): (tree: unknown, file: VFile) => void;
8
- //# sourceMappingURL=resolve-links.d.ts.map
@@ -18,4 +18,17 @@ export declare function buildSanitizeSchema(registry: ComponentRegistry, extend?
18
18
  * `anchorRel` option (default `noopener noreferrer`); a site can override it or disable it entirely.
19
19
  */
20
20
  export declare function rehypeAnchorRel(rel: string): (tree: Root) => void;
21
- //# sourceMappingURL=sanitize-schema.d.ts.map
21
+ /**
22
+ * Post-dispatch safety floor over the fully-built tree. The pre-dispatch rehype-sanitize floor
23
+ * cleans author content, but a component build() runs after it and can route a raw author
24
+ * attribute value into a sink. This guard runs last and neutralizes those sinks on every element
25
+ * no matter which plugin or which build() produced it: an unsafe URL scheme in a URL-bearing
26
+ * attribute, an inline on* event handler, or an inline style (stripped wholesale, matching the
27
+ * floor and cairn's class-driven styling). It is gated by the same unsafeDisableSanitize switch as
28
+ * the floor.
29
+ *
30
+ * The guard's boundary is the URL scheme check plus the on* and style strip. It does not remove a
31
+ * build()-emitted raw script, style, or iframe srcdoc element node. A build() that emits those is
32
+ * running site-developer code, and author markdown is cleaned by the pre-dispatch floor.
33
+ */
34
+ export declare function rehypeSinkGuard(): (tree: Root) => void;