@glw907/cairn-cms 0.60.1 → 0.62.2

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 (254) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/dist/components/AdminLayout.svelte +22 -0
  3. package/dist/components/CairnAdmin.svelte +3 -0
  4. package/dist/components/CairnTidySettings.svelte +2 -2
  5. package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
  6. package/dist/components/EditPage.svelte +116 -39
  7. package/dist/components/HelpHome.svelte +824 -0
  8. package/dist/components/HelpHome.svelte.d.ts +22 -0
  9. package/dist/components/MarkdownHelpDialog.svelte +4 -15
  10. package/dist/components/client-ingest.d.ts +16 -8
  11. package/dist/components/client-ingest.js +12 -6
  12. package/dist/components/editor-media.js +16 -8
  13. package/dist/components/editor-placeholder.d.ts +4 -2
  14. package/dist/components/editor-tidy.d.ts +24 -12
  15. package/dist/components/editor-tidy.js +8 -4
  16. package/dist/components/index.d.ts +1 -0
  17. package/dist/components/index.js +1 -0
  18. package/dist/components/link-completion.d.ts +12 -6
  19. package/dist/components/link-completion.js +12 -6
  20. package/dist/components/markdown-directives.d.ts +9 -6
  21. package/dist/components/markdown-directives.js +9 -6
  22. package/dist/components/markdown-format.d.ts +7 -2
  23. package/dist/components/markdown-format.js +59 -28
  24. package/dist/components/markdown-reference.d.ts +8 -0
  25. package/dist/components/markdown-reference.js +22 -0
  26. package/dist/components/media-upload-outcome.d.ts +12 -6
  27. package/dist/components/objective-errors.d.ts +8 -4
  28. package/dist/components/objective-errors.js +8 -4
  29. package/dist/components/preview-doc.d.ts +4 -2
  30. package/dist/components/preview-doc.js +4 -2
  31. package/dist/components/spellcheck.d.ts +55 -29
  32. package/dist/components/spellcheck.js +39 -21
  33. package/dist/components/tidy-categorize.d.ts +20 -10
  34. package/dist/components/tidy-categorize.js +16 -8
  35. package/dist/components/tidy-validate.d.ts +12 -6
  36. package/dist/components/tidy-validate.js +20 -10
  37. package/dist/components/topbar-context.d.ts +4 -2
  38. package/dist/content/advisories.d.ts +56 -0
  39. package/dist/content/advisories.js +87 -0
  40. package/dist/content/compose.d.ts +4 -2
  41. package/dist/content/compose.js +1 -0
  42. package/dist/content/excerpt.js +4 -2
  43. package/dist/content/getting-started.d.ts +18 -0
  44. package/dist/content/getting-started.js +12 -0
  45. package/dist/content/links.d.ts +16 -8
  46. package/dist/content/links.js +12 -6
  47. package/dist/content/manifest.d.ts +36 -18
  48. package/dist/content/manifest.js +32 -16
  49. package/dist/content/media-refs.d.ts +4 -2
  50. package/dist/content/media-refs.js +4 -2
  51. package/dist/content/media-rewrite.d.ts +8 -4
  52. package/dist/content/media-rewrite.js +76 -38
  53. package/dist/content/schema.d.ts +20 -10
  54. package/dist/content/site-dictionary.d.ts +4 -2
  55. package/dist/content/site-dictionary.js +8 -4
  56. package/dist/content/types.d.ts +97 -42
  57. package/dist/delivery/content-index.d.ts +16 -8
  58. package/dist/delivery/feeds.js +4 -2
  59. package/dist/delivery/json-ld.d.ts +3 -0
  60. package/dist/delivery/json-ld.js +3 -0
  61. package/dist/delivery/manifest.d.ts +4 -2
  62. package/dist/delivery/manifest.js +4 -2
  63. package/dist/delivery/public-routes.d.ts +12 -6
  64. package/dist/delivery/public-routes.js +4 -2
  65. package/dist/delivery/seo-fields.d.ts +12 -6
  66. package/dist/delivery/seo-fields.js +8 -4
  67. package/dist/delivery/site-indexes.d.ts +4 -2
  68. package/dist/delivery/site-resolver.d.ts +4 -2
  69. package/dist/delivery/site-resolver.js +4 -2
  70. package/dist/doctor/cloudflare-api.d.ts +6 -0
  71. package/dist/doctor/cloudflare-api.js +6 -0
  72. package/dist/doctor/index.d.ts +12 -6
  73. package/dist/doctor/report.d.ts +3 -0
  74. package/dist/doctor/report.js +3 -0
  75. package/dist/doctor/run.d.ts +3 -0
  76. package/dist/doctor/run.js +3 -0
  77. package/dist/doctor/types.d.ts +10 -2
  78. package/dist/doctor/types.js +6 -0
  79. package/dist/doctor/wrangler-config.d.ts +7 -2
  80. package/dist/doctor/wrangler-config.js +3 -0
  81. package/dist/email.d.ts +4 -2
  82. package/dist/env.d.ts +0 -3
  83. package/dist/env.js +0 -3
  84. package/dist/github/branches.d.ts +4 -2
  85. package/dist/github/branches.js +4 -2
  86. package/dist/github/signing.d.ts +1 -1
  87. package/dist/github/signing.js +2 -2
  88. package/dist/log/events.d.ts +1 -1
  89. package/dist/media/bulk-delete-plan.d.ts +8 -4
  90. package/dist/media/config.d.ts +12 -6
  91. package/dist/media/config.js +16 -8
  92. package/dist/media/delivery-bucket.d.ts +4 -2
  93. package/dist/media/library-entry.d.ts +4 -2
  94. package/dist/media/library-entry.js +4 -2
  95. package/dist/media/manifest.d.ts +29 -15
  96. package/dist/media/manifest.js +29 -16
  97. package/dist/media/naming.d.ts +12 -6
  98. package/dist/media/naming.js +24 -12
  99. package/dist/media/orphan-scan.d.ts +4 -2
  100. package/dist/media/reconcile.d.ts +21 -11
  101. package/dist/media/reconcile.js +12 -6
  102. package/dist/media/reference.d.ts +8 -4
  103. package/dist/media/reference.js +12 -6
  104. package/dist/media/rewrite-plan.d.ts +12 -6
  105. package/dist/media/sniff.d.ts +4 -2
  106. package/dist/media/sniff.js +28 -14
  107. package/dist/media/store.d.ts +16 -8
  108. package/dist/media/store.js +4 -2
  109. package/dist/media/transform-url.d.ts +12 -6
  110. package/dist/media/transform-url.js +8 -4
  111. package/dist/media/usage.d.ts +8 -4
  112. package/dist/nav/site-config.d.ts +16 -8
  113. package/dist/render/component-grammar.d.ts +23 -10
  114. package/dist/render/component-grammar.js +19 -8
  115. package/dist/render/component-insert.d.ts +8 -4
  116. package/dist/render/component-insert.js +4 -2
  117. package/dist/render/component-reference.d.ts +4 -2
  118. package/dist/render/component-reference.js +4 -2
  119. package/dist/render/component-validate.d.ts +3 -0
  120. package/dist/render/component-validate.js +3 -0
  121. package/dist/render/glyph.d.ts +4 -2
  122. package/dist/render/glyph.js +4 -2
  123. package/dist/render/pipeline.d.ts +20 -10
  124. package/dist/render/pipeline.js +4 -2
  125. package/dist/render/registry.d.ts +40 -20
  126. package/dist/render/registry.js +16 -8
  127. package/dist/render/rehype-dispatch.d.ts +22 -8
  128. package/dist/render/rehype-dispatch.js +22 -8
  129. package/dist/render/remark-directives.d.ts +3 -0
  130. package/dist/render/remark-directives.js +3 -0
  131. package/dist/render/remark-figure.d.ts +4 -2
  132. package/dist/render/remark-figure.js +4 -2
  133. package/dist/render/resolve-links.d.ts +4 -2
  134. package/dist/render/resolve-links.js +4 -2
  135. package/dist/render/resolve-media.d.ts +16 -8
  136. package/dist/render/resolve-media.js +12 -6
  137. package/dist/sveltekit/admin-dispatch.d.ts +2 -0
  138. package/dist/sveltekit/admin-dispatch.js +9 -3
  139. package/dist/sveltekit/auth-routes.d.ts +3 -0
  140. package/dist/sveltekit/auth-routes.js +3 -0
  141. package/dist/sveltekit/cairn-admin.d.ts +16 -5
  142. package/dist/sveltekit/cairn-admin.js +26 -10
  143. package/dist/sveltekit/content-routes.d.ts +191 -86
  144. package/dist/sveltekit/content-routes.js +297 -107
  145. package/dist/sveltekit/editors-routes.d.ts +3 -0
  146. package/dist/sveltekit/editors-routes.js +3 -0
  147. package/dist/sveltekit/guard.d.ts +4 -2
  148. package/dist/sveltekit/guard.js +4 -2
  149. package/dist/sveltekit/https-required-page.d.ts +1 -1
  150. package/dist/sveltekit/https-required-page.js +1 -1
  151. package/dist/sveltekit/index.d.ts +1 -1
  152. package/dist/sveltekit/media-route.d.ts +1 -2
  153. package/dist/sveltekit/media-route.js +13 -8
  154. package/dist/sveltekit/nav-routes.d.ts +7 -2
  155. package/dist/sveltekit/nav-routes.js +3 -0
  156. package/dist/sveltekit/types.d.ts +4 -2
  157. package/dist/vite/index.d.ts +32 -16
  158. package/dist/vite/index.js +52 -26
  159. package/dist/vite/resolve-root.d.ts +8 -4
  160. package/dist/vite/resolve-root.js +4 -2
  161. package/package.json +7 -1
  162. package/src/lib/components/AdminLayout.svelte +22 -0
  163. package/src/lib/components/CairnAdmin.svelte +3 -0
  164. package/src/lib/components/CairnTidySettings.svelte +2 -2
  165. package/src/lib/components/ComponentForm.svelte +0 -1
  166. package/src/lib/components/EditPage.svelte +133 -41
  167. package/src/lib/components/HelpHome.svelte +850 -0
  168. package/src/lib/components/MarkdownHelpDialog.svelte +4 -15
  169. package/src/lib/components/client-ingest.ts +20 -10
  170. package/src/lib/components/editor-media.ts +20 -10
  171. package/src/lib/components/editor-placeholder.ts +12 -6
  172. package/src/lib/components/editor-tidy.ts +28 -14
  173. package/src/lib/components/index.ts +1 -0
  174. package/src/lib/components/link-completion.ts +12 -6
  175. package/src/lib/components/markdown-directives.ts +13 -8
  176. package/src/lib/components/markdown-format.ts +63 -30
  177. package/src/lib/components/markdown-reference.ts +30 -0
  178. package/src/lib/components/media-upload-outcome.ts +12 -6
  179. package/src/lib/components/objective-errors.ts +16 -8
  180. package/src/lib/components/preview-doc.ts +4 -2
  181. package/src/lib/components/spellcheck.ts +79 -41
  182. package/src/lib/components/tidy-categorize.ts +28 -14
  183. package/src/lib/components/tidy-validate.ts +28 -14
  184. package/src/lib/components/topbar-context.ts +4 -2
  185. package/src/lib/content/advisories.ts +150 -0
  186. package/src/lib/content/compose.ts +5 -2
  187. package/src/lib/content/excerpt.ts +4 -2
  188. package/src/lib/content/getting-started.ts +31 -0
  189. package/src/lib/content/links.ts +16 -8
  190. package/src/lib/content/manifest.ts +36 -18
  191. package/src/lib/content/media-refs.ts +4 -2
  192. package/src/lib/content/media-rewrite.ts +100 -50
  193. package/src/lib/content/schema.ts +20 -10
  194. package/src/lib/content/site-dictionary.ts +8 -4
  195. package/src/lib/content/types.ts +97 -42
  196. package/src/lib/delivery/content-index.ts +16 -8
  197. package/src/lib/delivery/feeds.ts +4 -2
  198. package/src/lib/delivery/json-ld.ts +3 -0
  199. package/src/lib/delivery/manifest.ts +4 -2
  200. package/src/lib/delivery/public-routes.ts +16 -8
  201. package/src/lib/delivery/seo-fields.ts +12 -6
  202. package/src/lib/delivery/site-indexes.ts +4 -2
  203. package/src/lib/delivery/site-resolver.ts +4 -2
  204. package/src/lib/doctor/cloudflare-api.ts +6 -0
  205. package/src/lib/doctor/index.ts +12 -6
  206. package/src/lib/doctor/report.ts +3 -0
  207. package/src/lib/doctor/run.ts +3 -0
  208. package/src/lib/doctor/types.ts +10 -2
  209. package/src/lib/doctor/wrangler-config.ts +7 -2
  210. package/src/lib/email.ts +4 -2
  211. package/src/lib/env.ts +0 -3
  212. package/src/lib/github/branches.ts +4 -2
  213. package/src/lib/github/signing.ts +2 -2
  214. package/src/lib/log/events.ts +1 -0
  215. package/src/lib/media/bulk-delete-plan.ts +8 -4
  216. package/src/lib/media/config.ts +24 -12
  217. package/src/lib/media/delivery-bucket.ts +4 -2
  218. package/src/lib/media/library-entry.ts +4 -2
  219. package/src/lib/media/manifest.ts +33 -18
  220. package/src/lib/media/naming.ts +24 -12
  221. package/src/lib/media/orphan-scan.ts +4 -2
  222. package/src/lib/media/reconcile.ts +21 -11
  223. package/src/lib/media/reference.ts +12 -6
  224. package/src/lib/media/rewrite-plan.ts +12 -6
  225. package/src/lib/media/sniff.ts +28 -14
  226. package/src/lib/media/store.ts +16 -8
  227. package/src/lib/media/transform-url.ts +12 -6
  228. package/src/lib/media/usage.ts +8 -4
  229. package/src/lib/nav/site-config.ts +16 -8
  230. package/src/lib/render/component-grammar.ts +23 -10
  231. package/src/lib/render/component-insert.ts +8 -4
  232. package/src/lib/render/component-reference.ts +4 -2
  233. package/src/lib/render/component-validate.ts +3 -0
  234. package/src/lib/render/glyph.ts +4 -2
  235. package/src/lib/render/pipeline.ts +20 -10
  236. package/src/lib/render/registry.ts +44 -22
  237. package/src/lib/render/rehype-dispatch.ts +22 -8
  238. package/src/lib/render/remark-directives.ts +3 -0
  239. package/src/lib/render/remark-figure.ts +4 -2
  240. package/src/lib/render/resolve-links.ts +4 -2
  241. package/src/lib/render/resolve-media.ts +16 -8
  242. package/src/lib/sveltekit/admin-dispatch.ts +10 -4
  243. package/src/lib/sveltekit/auth-routes.ts +3 -0
  244. package/src/lib/sveltekit/cairn-admin.ts +37 -15
  245. package/src/lib/sveltekit/content-routes.ts +494 -197
  246. package/src/lib/sveltekit/editors-routes.ts +3 -0
  247. package/src/lib/sveltekit/guard.ts +4 -2
  248. package/src/lib/sveltekit/https-required-page.ts +1 -1
  249. package/src/lib/sveltekit/index.ts +3 -0
  250. package/src/lib/sveltekit/media-route.ts +13 -8
  251. package/src/lib/sveltekit/nav-routes.ts +7 -2
  252. package/src/lib/sveltekit/types.ts +4 -2
  253. package/src/lib/vite/index.ts +60 -30
  254. package/src/lib/vite/resolve-root.ts +8 -4
@@ -22,6 +22,11 @@ interface FieldBase {
22
22
  label: string;
23
23
  /** A required field fails validation when empty (spec §7.4). */
24
24
  required?: boolean;
25
+ /**
26
+ * One author-facing sentence shown under the field in the editor, in plain end-user language.
27
+ * Optional; render nothing when absent. Not a validation rule.
28
+ */
29
+ description?: string;
25
30
  }
26
31
 
27
32
  /** A single-line text input. */
@@ -33,8 +38,10 @@ export interface TextField extends FieldBase {
33
38
  max?: number;
34
39
  /** Exact required character length. */
35
40
  length?: number;
36
- /** A regular-expression source string the value must match. Stored as a string so the field
37
- * list stays plain serializable data; the validator compiles it. */
41
+ /**
42
+ * A regular-expression source string the value must match. Stored as a string so the field
43
+ * list stays plain serializable data; the validator compiles it.
44
+ */
38
45
  pattern?: string;
39
46
  }
40
47
  /** A multi-line text input. */
@@ -103,8 +110,10 @@ export type FrontmatterField =
103
110
  | FreeTagsField
104
111
  | ImageField;
105
112
 
106
- /** The stored value of an `image` field: a `media:` reference, a screen-reader description, and an
107
- * optional caption. */
113
+ /**
114
+ * The stored value of an `image` field: a `media:` reference, a screen-reader description, and an
115
+ * optional caption.
116
+ */
108
117
  export interface ImageValue {
109
118
  src: string;
110
119
  alt: string;
@@ -138,8 +147,10 @@ export interface ConceptConfig<S extends ConceptSchema = ConceptSchema> {
138
147
  singular?: string;
139
148
  /** The concept's schema: the form projection, the generated validator, and the inferred type. */
140
149
  schema: S;
141
- /** Frontmatter keys to surface on each `ContentSummary.fields`, so a list card reads an authored
142
- * field without a per-entry detail read. Each key should also be declared in `schema`. */
150
+ /**
151
+ * Frontmatter keys to surface on each `ContentSummary.fields`, so a list card reads an authored
152
+ * field without a per-entry detail read. Each key should also be declared in `schema`.
153
+ */
143
154
  summaryFields?: string[];
144
155
  }
145
156
 
@@ -191,28 +202,38 @@ export interface NavMenuConfig {
191
202
  * stylesheet.
192
203
  */
193
204
  export interface PreviewConfig {
194
- /** Absolute or root-relative URLs of the site's compiled stylesheets, linked inside the
195
- * preview document. A Vite `?url` import of the site's CSS resolves the hashed asset URL. */
205
+ /**
206
+ * Absolute or root-relative URLs of the site's compiled stylesheets, linked inside the
207
+ * preview document. A Vite `?url` import of the site's CSS resolves the hashed asset URL.
208
+ */
196
209
  stylesheets: string[];
197
210
  /** Class list applied to the preview document's body, for theme or typography roots. */
198
211
  bodyClass?: string;
199
- /** Class list for a wrapper element around the rendered content, reproducing the site's
200
- * content container (a prose or measure class). Omitted renders the content bare. */
212
+ /**
213
+ * Class list for a wrapper element around the rendered content, reproducing the site's
214
+ * content container (a prose or measure class). Omitted renders the content bare.
215
+ */
201
216
  containerClass?: string;
202
- /** Per-concept overrides of bodyClass and containerClass, keyed by concept id. An entry's
217
+ /**
218
+ * Per-concept overrides of bodyClass and containerClass, keyed by concept id. An entry's
203
219
  * preview resolves the override for its concept over the top-level values; stylesheets are
204
- * always shared. */
220
+ * always shared.
221
+ */
205
222
  byConcept?: Record<string, { bodyClass?: string; containerClass?: string }>;
206
223
  }
207
224
 
208
- /** The flat preview shape `editLoad` ships to the edit page: the top-level `PreviewConfig`
209
- * values with the entry's concept override applied, and no `byConcept` map. */
225
+ /**
226
+ * The flat preview shape `editLoad` ships to the edit page: the top-level `PreviewConfig`
227
+ * values with the entry's concept override applied, and no `byConcept` map.
228
+ */
210
229
  export type ResolvedPreview = Omit<PreviewConfig, 'byConcept'>;
211
230
 
212
- /** A site's media configuration (seam 4). A site sets this to turn on R2-backed media: uploads,
231
+ /**
232
+ * A site's media configuration (seam 4). A site sets this to turn on R2-backed media: uploads,
213
233
  * content-addressed storage, and Cloudflare Images variants. Omitting it leaves media off. The
214
234
  * engine normalizes this into a `ResolvedAssetConfig` and merges the named variants over the
215
- * built-in thumb, inline, card, and hero presets. */
235
+ * built-in thumb, inline, card, and hero presets.
236
+ */
216
237
  export interface AssetConfig {
217
238
  /** The R2 bucket binding name on the Worker, e.g. "MEDIA_BUCKET". Required when a site declares media. */
218
239
  bucketBinding: string;
@@ -226,10 +247,12 @@ export interface AssetConfig {
226
247
  allowedTypes?: string[];
227
248
  /** Named transform presets, merged over the built-in thumb/inline/card/hero presets. */
228
249
  variants?: Record<string, VariantSpec>;
229
- /** Whether Cloudflare Image Transformations are enabled for the zone (default false). The feature
250
+ /**
251
+ * Whether Cloudflare Image Transformations are enabled for the zone (default false). The feature
230
252
  * is a per-zone setting that the dashboard or API turns on; it cannot be flipped from a Worker. With
231
253
  * it off, the media resolver serves the bare full-size delivery path and ignores any preset, so
232
- * thumbnails stay correct (full-size-but-correct) rather than pointing at a dead /cdn-cgi/image URL. */
254
+ * thumbnails stay correct (full-size-but-correct) rather than pointing at a dead /cdn-cgi/image URL.
255
+ */
233
256
  transformations?: boolean;
234
257
  }
235
258
 
@@ -246,10 +269,18 @@ export interface CairnAdapter {
246
269
  };
247
270
  backend: BackendConfig;
248
271
  sender: SenderConfig;
249
- /** The site's one renderer: the editor preview and every public page call it (design decision 4).
272
+ /**
273
+ * Optional contact a stuck editor is pointed to from the in-admin help (an email address, a URL,
274
+ * or a name and instruction). The help renders the hand-off only when this is set. Plain string,
275
+ * passed through verbatim.
276
+ */
277
+ supportContact?: string;
278
+ /**
279
+ * The site's one renderer: the editor preview and every public page call it (design decision 4).
250
280
  * `resolve` rewrites cairn: links to live permalinks; the build passes a site-resolver-backed
251
281
  * one, the preview a manifest one. The trailing `resolveMedia` is additive and optional: the build
252
- * passes a site-resolver-backed media resolver, the preview a manifest-backed one. */
282
+ * passes a site-resolver-backed media resolver, the preview a manifest-backed one.
283
+ */
253
284
  render(
254
285
  md: string,
255
286
  opts?: {
@@ -258,24 +289,32 @@ export interface CairnAdapter {
258
289
  resolveMedia?: import('../render/resolve-media.js').MediaResolve;
259
290
  },
260
291
  ): string | Promise<string>;
261
- /** Repo-relative path to the committed content manifest. Defaults to src/content/.cairn/index.json
262
- * in composeRuntime. It sits outside any concept directory, so content enumeration never globs it. */
292
+ /**
293
+ * Repo-relative path to the committed content manifest. Defaults to src/content/.cairn/index.json
294
+ * in composeRuntime. It sits outside any concept directory, so content enumeration never globs it.
295
+ */
263
296
  manifestPath?: string;
264
- /** Repo-relative path to the committed media manifest. Defaults to src/content/.cairn/media.json,
265
- * applied in composeRuntime. Sits outside any concept directory, like the content manifest. */
297
+ /**
298
+ * Repo-relative path to the committed media manifest. Defaults to src/content/.cairn/media.json,
299
+ * applied in composeRuntime. Sits outside any concept directory, like the content manifest.
300
+ */
266
301
  mediaManifestPath?: string;
267
- /** Repo-relative path to the committed personal dictionary file. Defaults to
302
+ /**
303
+ * Repo-relative path to the committed personal dictionary file. Defaults to
268
304
  * src/content/.cairn/dictionary.txt, applied in composeRuntime: the same `.cairn/` content root the
269
305
  * manifests use, so the spec's `content/.cairn/dictionary.txt` resolves the same configurable way the
270
- * manifest paths do. One word per line, sorted, comment lines allowed (see site-dictionary.ts). */
306
+ * manifest paths do. One word per line, sorted, comment lines allowed (see site-dictionary.ts).
307
+ */
271
308
  dictionaryPath?: string;
272
309
  /** Directive component registry; the renderer and the future palette derive from it (seam 3). */
273
310
  registry?: ComponentRegistry;
274
311
  /** The site's glyph name to SVG path-data map, for the admin icon picker and the renderer. */
275
312
  icons?: IconSet;
276
313
  navMenu?: NavMenuConfig;
277
- /** The live site's content styling for the preview frame. The admin's chrome isolation keeps
278
- * the site's CSS out of the admin document, so the preview frame links these instead. */
314
+ /**
315
+ * The live site's content styling for the preview frame. The admin's chrome isolation keeps
316
+ * the site's CSS out of the admin document, so the preview frame links these instead.
317
+ */
279
318
  preview?: PreviewConfig;
280
319
  assets?: AssetConfig;
281
320
  }
@@ -301,8 +340,10 @@ export interface ConceptDescriptor {
301
340
  /** Concept id, the key under `content`, e.g. "posts". */
302
341
  id: string;
303
342
  label: string;
304
- /** The singular noun for the create affordances ("New post"); resolved from `ConceptConfig.singular`,
305
- * defaulting to `label` when the config omits it. */
343
+ /**
344
+ * The singular noun for the create affordances ("New post"); resolved from `ConceptConfig.singular`,
345
+ * defaulting to `label` when the config omits it.
346
+ */
306
347
  singular: string;
307
348
  dir: string;
308
349
  routing: RoutingRule;
@@ -311,8 +352,10 @@ export interface ConceptDescriptor {
311
352
  /** Filename date-prefix granularity for a dated concept; resolved by `normalizeConcepts`. */
312
353
  datePrefix: DatePrefix;
313
354
  fields: FrontmatterField[];
314
- /** Frontmatter keys the index copies onto each summary's `fields` record. `normalizeConcepts`
315
- * resolves it to `[]` when a concept omits `summaryFields`. */
355
+ /**
356
+ * Frontmatter keys the index copies onto each summary's `fields` record. `normalizeConcepts`
357
+ * resolves it to `[]` when a concept omits `summaryFields`.
358
+ */
316
359
  summaryFields: string[];
317
360
  validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
318
361
  }
@@ -371,9 +414,13 @@ export interface CairnRuntime {
371
414
  concepts: ConceptDescriptor[];
372
415
  backend: BackendConfig;
373
416
  sender: SenderConfig;
374
- /** The site's one renderer: the editor preview and every public page call it (design decision 4).
417
+ /** The support contact passed through from the adapter; the in-admin help reads it. Optional. */
418
+ supportContact?: string;
419
+ /**
420
+ * The site's one renderer: the editor preview and every public page call it (design decision 4).
375
421
  * The trailing `resolveMedia` is additive and optional: the build passes a site-resolver-backed
376
- * media resolver, the preview a manifest-backed one. */
422
+ * media resolver, the preview a manifest-backed one.
423
+ */
377
424
  render(
378
425
  md: string,
379
426
  opts?: {
@@ -385,15 +432,19 @@ export interface CairnRuntime {
385
432
  manifestPath: string;
386
433
  /** The repo-relative path to the committed media manifest, defaulted in composeRuntime. */
387
434
  mediaManifestPath: string;
388
- /** The repo-relative path to the committed personal dictionary file (one word per line, sorted),
435
+ /**
436
+ * The repo-relative path to the committed personal dictionary file (one word per line, sorted),
389
437
  * defaulted in composeRuntime to src/content/.cairn/dictionary.txt: the same `.cairn/` content root
390
438
  * the manifests use. The edit load reads it and threads its words onto EditData; the
391
439
  * addDictionaryWord action reads, merges, and commits it. Optional on the runtime so a hand-built
392
440
  * runtime need not set it; composeRuntime always fills it, and the edit load and the action default
393
- * a missing value to the same content-root path. */
441
+ * a missing value to the same content-root path.
442
+ */
394
443
  dictionaryPath?: string;
395
- /** The adapter's asset config resolved once at compose: `{ enabled: false }` for a no-media site,
396
- * otherwise the filled config the upload, storage, delivery, and resolver paths read. */
444
+ /**
445
+ * The adapter's asset config resolved once at compose: `{ enabled: false }` for a no-media site,
446
+ * otherwise the filled config the upload, storage, delivery, and resolver paths read.
447
+ */
397
448
  resolvedAssets: import('../media/config.js').ResolvedAssetConfig;
398
449
  registry?: ComponentRegistry;
399
450
  /** The site's glyph name to SVG path-data map, for the admin icon picker and the renderer. */
@@ -402,18 +453,22 @@ export interface CairnRuntime {
402
453
  /** The live site's content styling for the preview frame; passed through from the adapter. */
403
454
  preview?: PreviewConfig;
404
455
  assets?: AssetConfig;
405
- /** The editor's spellcheck dictionary file, resolved once at compose from the site config's
456
+ /**
457
+ * The editor's spellcheck dictionary file, resolved once at compose from the site config's
406
458
  * `spellcheck.dialect` (defaulting to US English). The edit load threads it onto EditData and the
407
459
  * editor resolves it to a real asset URL on the main thread, so the Worker receives the URL and
408
460
  * never reads config. Just the filename, e.g. "dictionary-en-us.txt". Optional on the runtime so a
409
461
  * hand-built runtime need not set it; composeRuntime always fills it, and the edit load defaults a
410
- * missing value to the US English dictionary. */
462
+ * missing value to the US English dictionary.
463
+ */
411
464
  spellcheckDictionary?: string;
412
- /** The editor tidy (LLM copy-edit) settings, passed through from the site config. Optional on the
465
+ /**
466
+ * The editor tidy (LLM copy-edit) settings, passed through from the site config. Optional on the
413
467
  * runtime so a hand-built runtime need not set it; composeRuntime threads it from
414
468
  * `siteConfig.tidy`. The tidy action reads `enabled` and `model` at call time, and builds its prompt
415
469
  * from `conventions`. Absent (or `enabled` false) means tidy is off, and the action refuses with a
416
- * fail(503) before any model call. */
470
+ * fail(503) before any model call.
471
+ */
417
472
  tidy?: import('../nav/site-config.js').TidyConfig;
418
473
  /** Admin panels contributed by extensions (Mode 2). Empty until Plan 09 wires the dispatch route. */
419
474
  adminPanels?: AdminPanel[];
@@ -15,8 +15,10 @@ export interface RawFile {
15
15
 
16
16
  /** The cheap, plain-data view of one entry, for lists, feeds, and the sitemap. */
17
17
  export interface ContentSummary {
18
- /** The descriptor id this entry belongs to, e.g. "posts". Lets a list or page branch per
19
- * concept without re-deriving it from a proxy like `entry.date`. */
18
+ /**
19
+ * The descriptor id this entry belongs to, e.g. "posts". Lets a list or page branch per
20
+ * concept without re-deriving it from a proxy like `entry.date`.
21
+ */
20
22
  concept: string;
21
23
  id: string;
22
24
  slug: string;
@@ -24,23 +26,29 @@ export interface ContentSummary {
24
26
  title: string;
25
27
  date?: string;
26
28
  updated?: string;
27
- /** The entry's tags, always present as an array and empty when the file declares none. This is the
29
+ /**
30
+ * The entry's tags, always present as an array and empty when the file declares none. This is the
28
31
  * read-model normalization. It differs on purpose from the validated `frontmatter.tags`, which the
29
32
  * validator omits when empty, so a published file carries no `tags: []` noise. Read `tags` here for
30
- * a list; read `frontmatter.tags` only when you need the validated, possibly-absent value. */
33
+ * a list; read `frontmatter.tags` only when you need the validated, possibly-absent value.
34
+ */
31
35
  tags: string[];
32
36
  excerpt: string;
33
37
  wordCount: number;
34
38
  draft: boolean;
35
- /** The frontmatter keys the descriptor nominated via `summaryFields`, read off the validated,
39
+ /**
40
+ * The frontmatter keys the descriptor nominated via `summaryFields`, read off the validated,
36
41
  * normalized frontmatter. Held in a separate record so a nominated key cannot collide with a
37
- * typed summary field. Empty when the concept declares no `summaryFields`. */
42
+ * typed summary field. Empty when the concept declares no `summaryFields`.
43
+ */
38
44
  fields: Record<string, unknown>;
39
45
  }
40
46
 
41
- /** The detail view: a summary plus the frontmatter and the body to render. The frontmatter
47
+ /**
48
+ * The detail view: a summary plus the frontmatter and the body to render. The frontmatter
42
49
  * type defaults to `Record<string, unknown>`; the typed-reads pass infers it from the concept
43
- * fields. Generic now so that change does not break this signature. */
50
+ * fields. Generic now so that change does not break this signature.
51
+ */
44
52
  export interface ContentEntry<F = Record<string, unknown>> extends ContentSummary {
45
53
  frontmatter: F;
46
54
  body: string;
@@ -30,8 +30,10 @@ function cdataSafe(value: string): string {
30
30
  return value.replace(/]]>/g, ']]]]><![CDATA[>');
31
31
  }
32
32
 
33
- /** Parse a YYYY-MM-DD (or ISO) string as a UTC instant. Returns undefined for an absent or
34
- * unparseable date, so a feed omits the date field rather than emit Invalid Date or throw. */
33
+ /**
34
+ * Parse a YYYY-MM-DD (or ISO) string as a UTC instant. Returns undefined for an absent or
35
+ * unparseable date, so a feed omits the date field rather than emit Invalid Date or throw.
36
+ */
35
37
  function parseFeedDate(date?: string): Date | undefined {
36
38
  if (!date) return undefined;
37
39
  const at = new Date(`${date.slice(0, 10)}T00:00:00.000Z`);
@@ -5,6 +5,9 @@
5
5
  // The line separator U+2028 and paragraph separator U+2029 get the same treatment: they are
6
6
  // legal inside a JSON string but unsafe in inline script text, where some parsers read them as
7
7
  // line terminators, so an author pasting one into frontmatter would corrupt the JSON-LD block.
8
+ /**
9
+ *
10
+ */
8
11
  export function jsonLdScript(data: Record<string, unknown>): string {
9
12
  const json = JSON.stringify(data)
10
13
  .replace(/</g, '\\u003c')
@@ -11,8 +11,10 @@ import type { SiteConfig } from '../nav/site-config.js';
11
11
  import type { CairnAdapter } from '../content/types.js';
12
12
  import type { SiteGlobs } from './site-indexes.js';
13
13
 
14
- /** Build the whole-corpus manifest from a site's adapter, config, and per-concept globs. Drafts are
15
- * included and flagged, so the admin picker and the guards see the full graph. */
14
+ /**
15
+ * Build the whole-corpus manifest from a site's adapter, config, and per-concept globs. Drafts are
16
+ * included and flagged, so the admin picker and the guards see the full graph.
17
+ */
16
18
  export function buildSiteManifest<A extends CairnAdapter>(adapter: A, config: SiteConfig, globs: SiteGlobs<A>): Manifest {
17
19
  const globRecord = globs as Record<string, Record<string, string> | undefined>;
18
20
  const manifest = emptyManifest();
@@ -25,12 +25,16 @@ export interface PublicRoutesDeps {
25
25
  description: string;
26
26
  /** Absolute feed URLs for the head's autodiscovery links. */
27
27
  feeds?: { rss?: string; json?: string };
28
- /** A site-wide default OG image, used when an entry declares none. Resolved to absolute like the
29
- * canonical URL, so a relative path such as "/og/default.png" works. */
28
+ /**
29
+ * A site-wide default OG image, used when an entry declares none. Resolved to absolute like the
30
+ * canonical URL, so a relative path such as "/og/default.png" works.
31
+ */
30
32
  defaultImage?: string;
31
- /** Resolve a frontmatter `media:` hero reference to its delivery path. The site builds this from its
33
+ /**
34
+ * Resolve a frontmatter `media:` hero reference to its delivery path. The site builds this from its
32
35
  * committed `media.json` exactly as it builds the body resolver (`makeMediaResolver`). When absent,
33
- * media is off and no `heroImage` projection is derived. */
36
+ * media is off and no `heroImage` projection is derived.
37
+ */
34
38
  resolveMedia?: MediaResolve;
35
39
  }
36
40
 
@@ -58,11 +62,13 @@ export interface EntryData {
58
62
  seo: SeoMeta;
59
63
  newer?: ContentSummary;
60
64
  older?: ContentSummary;
61
- /** The resolved hero image, a derived projection of the frontmatter `image` field. `url` is the
65
+ /**
66
+ * The resolved hero image, a derived projection of the frontmatter `image` field. `url` is the
62
67
  * root-relative delivery path for an `<img>`, `absoluteUrl` the origin-anchored form for the
63
68
  * og:image, and `alt`/`caption` carry from the stored object. The canonical token is untouched:
64
69
  * `entry.frontmatter.image.src` stays the `media:` token. Undefined when no hero is set, media is
65
- * off, the reference does not parse, or the resolver finds no asset. */
70
+ * off, the reference does not parse, or the resolver finds no asset.
71
+ */
66
72
  heroImage?: { url: string; absoluteUrl?: string; alt: string; caption?: string };
67
73
  }
68
74
 
@@ -70,7 +76,8 @@ export interface EntryData {
70
76
  export function createPublicRoutes(deps: PublicRoutesDeps) {
71
77
  const { site, render, origin, siteName, description, feeds, defaultImage, resolveMedia } = deps;
72
78
 
73
- /** Derive the hero projection from an entry's frontmatter, without mutating it (locked decision 5).
79
+ /**
80
+ * Derive the hero projection from an entry's frontmatter, without mutating it (locked decision 5).
74
81
  * The hero lives at the conventional `image` key as the validated nested object `{ src, alt, caption }`;
75
82
  * only an image field's validate arm produces an object-with-string-`src` shape, so detecting that
76
83
  * structure is enough (a text field stores a string, a tags field an array). Returns undefined when
@@ -81,7 +88,8 @@ export function createPublicRoutes(deps: PublicRoutesDeps) {
81
88
  * and renders in the editor, but its delivery resolution is not wired here yet, since the field
82
89
  * declarations are not reachable in the delivery read path. Honoring a renamed `seo`-flagged field
83
90
  * (and a second image field per concept) at delivery is a carried follow-up; every consumer today
84
- * uses `image`. */
91
+ * uses `image`.
92
+ */
85
93
  function deriveHeroImage(frontmatter: Record<string, unknown>): EntryData['heroImage'] {
86
94
  if (!resolveMedia) return undefined;
87
95
  const value = frontmatter.image;
@@ -3,9 +3,11 @@
3
3
  // typed Record<string, unknown>; this reads the known head fields by name and coerces. Kept apart
4
4
  // from seo.ts (the head builder) so reading frontmatter and building the head stay distinct concerns.
5
5
 
6
- /** The head fields a concept can carry in frontmatter. Each is optional and omitted when absent.
6
+ /**
7
+ * The head fields a concept can carry in frontmatter. Each is optional and omitted when absent.
7
8
  * `author` is article-scoped downstream: the head builder emits `article:author` only for a dated
8
- * entry, so an `author` on an undated Page is read here but not rendered. */
9
+ * entry, so an `author` on an undated Page is read here but not rendered.
10
+ */
9
11
  export interface SeoFields {
10
12
  description?: string;
11
13
  image?: string;
@@ -15,11 +17,13 @@ export interface SeoFields {
15
17
 
16
18
  const KEYS = ['description', 'image', 'robots', 'author'] as const;
17
19
 
18
- /** Read the known SEO head fields off an entry's normalized frontmatter. Keeps a present string,
20
+ /**
21
+ * Read the known SEO head fields off an entry's normalized frontmatter. Keeps a present string,
19
22
  * trimmed, and omits an absent, empty, or non-string value. Trimming the stored value keeps a stray
20
23
  * `robots: " noindex "` from reaching the head tag with surrounding whitespace. The field must be
21
24
  * declared in the concept's schema to survive the validate-once read; an undeclared key is not on the
22
- * normalized frontmatter. */
25
+ * normalized frontmatter.
26
+ */
23
27
  export function readSeoFields(frontmatter: Record<string, unknown>): SeoFields {
24
28
  const fields: SeoFields = {};
25
29
  for (const key of KEYS) {
@@ -29,11 +33,13 @@ export function readSeoFields(frontmatter: Record<string, unknown>): SeoFields {
29
33
  return fields;
30
34
  }
31
35
 
32
- /** Resolve an author-supplied image path to an absolute URL against the site origin. An absolute or
36
+ /**
37
+ * Resolve an author-supplied image path to an absolute URL against the site origin. An absolute or
33
38
  * protocol-relative URL passes through; a root-relative path anchors to the origin; a malformed
34
39
  * string returns undefined rather than throwing at build. The sites use a bare-domain origin, so a
35
40
  * bare path also anchors to the origin root; against a sub-path origin it would resolve relative to
36
- * that path, per the WHATWG URL rules. */
41
+ * that path, per the WHATWG URL rules.
42
+ */
37
43
  export function resolveImageUrl(image: string, origin: string): string | undefined {
38
44
  try {
39
45
  const url = new URL(image, origin);
@@ -18,8 +18,10 @@ export type SiteGlobs<A extends CairnAdapter> = {
18
18
  [K in keyof A['content']]?: Record<string, string>;
19
19
  };
20
20
 
21
- /** The typed per-concept indexes plus the cross-concept `site` resolver. A concept literally named
22
- * `site` is not supported, since `site` is the reserved resolver key. */
21
+ /**
22
+ * The typed per-concept indexes plus the cross-concept `site` resolver. A concept literally named
23
+ * `site` is not supported, since `site` is the reserved resolver key.
24
+ */
23
25
  export type SiteIndexes<A extends CairnAdapter> = {
24
26
  [K in keyof A['content']]: ContentIndex<
25
27
  NonNullable<A['content'][K]> extends ConceptConfig<infer S> ? Infer<S> : Record<string, unknown>
@@ -93,8 +93,10 @@ export function createSiteResolver(concepts: ConceptIndex[], opts: { validate?:
93
93
  };
94
94
  }
95
95
 
96
- /** A resolver backed by the site resolver, for the build. A miss throws, so a dangling cairn: token
97
- * fails the prerender (the build backstop). The preview uses manifestLinkResolver, which marks. */
96
+ /**
97
+ * A resolver backed by the site resolver, for the build. A miss throws, so a dangling cairn: token
98
+ * fails the prerender (the build backstop). The preview uses manifestLinkResolver, which marks.
99
+ */
98
100
  export function buildLinkResolver(site: SiteResolver): LinkResolve {
99
101
  return (ref) => {
100
102
  const url = site.concept(ref.concept)?.byId(ref.id)?.permalink;
@@ -17,12 +17,18 @@ export const NO_ACCOUNT: CheckResult = skip(
17
17
  'set CLOUDFLARE_API_TOKEN, and CLOUDFLARE_ACCOUNT_ID or a wrangler account_id, to run this check'
18
18
  );
19
19
 
20
+ /**
21
+ *
22
+ */
20
23
  export function cfGet(ctx: DoctorContext, path: string): Promise<Response> {
21
24
  return ctx.fetch(`${CF_API}${path}`, {
22
25
  headers: { authorization: `Bearer ${ctx.cfToken}` },
23
26
  });
24
27
  }
25
28
 
29
+ /**
30
+ *
31
+ */
26
32
  export function cfPost(ctx: DoctorContext, path: string, body: unknown): Promise<Response> {
27
33
  return ctx.fetch(`${CF_API}${path}`, {
28
34
  method: 'POST',
@@ -25,8 +25,10 @@ export interface DoctorArgs {
25
25
  from?: string;
26
26
  repo?: string;
27
27
  sendTest?: string;
28
- /** The live admin probe: a URL when --probe carried one, true for the bare flag (probe the
29
- * PUBLIC_ORIGIN input), absent when the flag never appeared (the probe does not run). */
28
+ /**
29
+ * The live admin probe: a URL when --probe carried one, true for the bare flag (probe the
30
+ * PUBLIC_ORIGIN input), absent when the flag never appeared (the probe does not run).
31
+ */
30
32
  probe?: string | true;
31
33
  }
32
34
 
@@ -91,12 +93,16 @@ export function contextFromEnv(
91
93
  };
92
94
  }
93
95
 
94
- /** The lazy derivation sources the bin wires up: the adapter read through the consumer's own
96
+ /**
97
+ * The lazy derivation sources the bin wires up: the adapter read through the consumer's own
95
98
  * Vite resolution and the wrangler config's account_id. Each runs only when an input it feeds
96
- * is still missing, so a doctor run with full flags touches neither. */
99
+ * is still missing, so a doctor run with full flags touches neither.
100
+ */
97
101
  export interface DerivationSources {
98
- /** Returns { owner, repo, from, mediaBucketBinding } off the adapter, or null when nothing is
99
- * derivable. */
102
+ /**
103
+ * Returns `{ owner, repo, from, mediaBucketBinding }` off the adapter, or null when nothing is
104
+ * derivable.
105
+ */
100
106
  adapterFacts: () => Promise<{
101
107
  owner?: string;
102
108
  repo?: string;
@@ -11,6 +11,9 @@ const TAG: Record<CheckResult['status'], string> = {
11
11
  skip: 'SKIP',
12
12
  };
13
13
 
14
+ /**
15
+ *
16
+ */
14
17
  export function formatReport(results: { check: DoctorCheck; result: CheckResult }[]): string {
15
18
  const lines = results.map(
16
19
  ({ check, result }) => `${TAG[result.status]} ${check.title}: ${result.detail}`
@@ -3,6 +3,9 @@
3
3
  import { fail } from './types.js';
4
4
  import type { CheckResult, DoctorCheck, DoctorContext } from './types.js';
5
5
 
6
+ /**
7
+ *
8
+ */
6
9
  export async function runDoctor(
7
10
  checks: DoctorCheck[],
8
11
  ctx: DoctorContext
@@ -14,10 +14,16 @@ export function pass(detail: string): CheckResult {
14
14
  return { status: 'pass', detail };
15
15
  }
16
16
 
17
+ /**
18
+ *
19
+ */
17
20
  export function fail(detail: string): CheckResult {
18
21
  return { status: 'fail', detail };
19
22
  }
20
23
 
24
+ /**
25
+ *
26
+ */
21
27
  export function skip(detail: string): CheckResult {
22
28
  return { status: 'skip', detail };
23
29
  }
@@ -45,8 +51,10 @@ export interface DoctorContext {
45
51
  cfAccountId?: string;
46
52
  /** PUBLIC_ORIGIN, the env fallback when the wrangler vars carry none. */
47
53
  publicOrigin?: string;
48
- /** The adapter's media bucket binding (cairn.assets.bucketBinding), derived off the adapter.
49
- * Undefined when the site declares no media assets; the media-bucket check skips in that case. */
54
+ /**
55
+ * The adapter's media bucket binding (cairn.assets.bucketBinding), derived off the adapter.
56
+ * Undefined when the site declares no media assets; the media-bucket check skips in that case.
57
+ */
50
58
  mediaBucketBinding?: string;
51
59
  /** GITHUB_APP_ID / GITHUB_APP_INSTALLATION_ID / GITHUB_APP_PRIVATE_KEY_B64. */
52
60
  github?: { appId: string; installationId: string; privateKeyB64: string };
@@ -16,11 +16,16 @@ export interface WranglerFacts {
16
16
  publicOrigin?: string;
17
17
  /** The top-level account_id, when declared; a fallback for CLOUDFLARE_ACCOUNT_ID. */
18
18
  accountId?: string;
19
- /** The declared r2_buckets binding names; the conditional media check matches the adapter's
20
- * bucketBinding against this. Not part of the hard config.bindings check (decision 9). */
19
+ /**
20
+ * The declared r2_buckets binding names; the conditional media check matches the adapter's
21
+ * bucketBinding against this. Not part of the hard config.bindings check (decision 9).
22
+ */
21
23
  r2Buckets: string[];
22
24
  }
23
25
 
26
+ /**
27
+ *
28
+ */
24
29
  export async function readWranglerConfig(
25
30
  readFile: DoctorContext['readFile']
26
31
  ): Promise<WranglerFacts | null> {
package/src/lib/email.ts CHANGED
@@ -23,9 +23,11 @@ export interface AuthBranding {
23
23
  replyTo?: string;
24
24
  }
25
25
 
26
- /** The injected send. Production uses `cloudflareSend`; tests pass a sink. A thrown error's
26
+ /**
27
+ * The injected send. Production uses `cloudflareSend`; tests pass a sink. A thrown error's
27
28
  * text reaches the structured log (scrubbed and truncated), so a custom sender must not embed
28
- * the message body or the magic link in what it throws. */
29
+ * the message body or the magic link in what it throws.
30
+ */
29
31
  export type SendMagicLink = (env: AuthEnv, message: MagicLinkMessage) => Promise<void>;
30
32
 
31
33
  /** Build the confirmation email. The link is the only action; the copy stays plain. */