@glw907/cairn-cms 0.60.0 → 0.62.1

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 (281) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/components/AdminLayout.svelte +152 -229
  3. package/dist/components/CairnAdmin.svelte +13 -42
  4. package/dist/components/CairnLogo.svelte +1 -6
  5. package/dist/components/CairnMediaLibrary.svelte +821 -1210
  6. package/dist/components/CairnTidySettings.svelte +194 -261
  7. package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
  8. package/dist/components/ComponentForm.svelte +110 -185
  9. package/dist/components/ComponentInsertDialog.svelte +163 -283
  10. package/dist/components/ConceptList.svelte +111 -191
  11. package/dist/components/ConfirmPage.svelte +5 -12
  12. package/dist/components/CsrfField.svelte +5 -11
  13. package/dist/components/DeleteDialog.svelte +15 -42
  14. package/dist/components/EditPage.svelte +781 -1205
  15. package/dist/components/EditorToolbar.svelte +108 -170
  16. package/dist/components/HelpHome.svelte +824 -0
  17. package/dist/components/HelpHome.svelte.d.ts +22 -0
  18. package/dist/components/IconPicker.svelte +23 -53
  19. package/dist/components/LinkPicker.svelte +34 -58
  20. package/dist/components/LoginPage.svelte +14 -27
  21. package/dist/components/ManageEditors.svelte +3 -15
  22. package/dist/components/MarkdownEditor.svelte +689 -957
  23. package/dist/components/MarkdownHelpDialog.svelte +12 -27
  24. package/dist/components/MediaCaptureCard.svelte +18 -57
  25. package/dist/components/MediaFigureControl.svelte +32 -71
  26. package/dist/components/MediaHeroField.svelte +210 -329
  27. package/dist/components/MediaInsertPopover.svelte +156 -283
  28. package/dist/components/MediaPicker.svelte +67 -131
  29. package/dist/components/NavTree.svelte +46 -78
  30. package/dist/components/RenameDialog.svelte +16 -43
  31. package/dist/components/ShortcutsDialog.svelte +9 -13
  32. package/dist/components/ShortcutsGrid.svelte +1 -2
  33. package/dist/components/TidyReview.svelte +140 -248
  34. package/dist/components/WebLinkDialog.svelte +19 -40
  35. package/dist/components/cairn-admin.css +4 -0
  36. package/dist/components/client-ingest.d.ts +16 -8
  37. package/dist/components/client-ingest.js +12 -6
  38. package/dist/components/editor-media.js +16 -8
  39. package/dist/components/editor-placeholder.d.ts +4 -2
  40. package/dist/components/editor-tidy.d.ts +24 -12
  41. package/dist/components/editor-tidy.js +8 -4
  42. package/dist/components/index.d.ts +1 -0
  43. package/dist/components/index.js +1 -0
  44. package/dist/components/link-completion.d.ts +12 -6
  45. package/dist/components/link-completion.js +12 -6
  46. package/dist/components/markdown-directives.d.ts +9 -6
  47. package/dist/components/markdown-directives.js +9 -6
  48. package/dist/components/markdown-format.d.ts +7 -2
  49. package/dist/components/markdown-format.js +59 -28
  50. package/dist/components/markdown-reference.d.ts +8 -0
  51. package/dist/components/markdown-reference.js +22 -0
  52. package/dist/components/media-upload-outcome.d.ts +12 -6
  53. package/dist/components/objective-errors.d.ts +8 -4
  54. package/dist/components/objective-errors.js +8 -4
  55. package/dist/components/preview-doc.d.ts +4 -2
  56. package/dist/components/preview-doc.js +4 -2
  57. package/dist/components/spellcheck.d.ts +57 -29
  58. package/dist/components/spellcheck.js +50 -20
  59. package/dist/components/tidy-categorize.d.ts +20 -10
  60. package/dist/components/tidy-categorize.js +16 -8
  61. package/dist/components/tidy-validate.d.ts +12 -6
  62. package/dist/components/tidy-validate.js +20 -10
  63. package/dist/components/topbar-context.d.ts +4 -2
  64. package/dist/content/advisories.d.ts +51 -0
  65. package/dist/content/advisories.js +79 -0
  66. package/dist/content/compose.d.ts +4 -2
  67. package/dist/content/compose.js +1 -0
  68. package/dist/content/excerpt.js +4 -2
  69. package/dist/content/getting-started.d.ts +18 -0
  70. package/dist/content/getting-started.js +12 -0
  71. package/dist/content/links.d.ts +16 -8
  72. package/dist/content/links.js +12 -6
  73. package/dist/content/manifest.d.ts +36 -18
  74. package/dist/content/manifest.js +32 -16
  75. package/dist/content/media-refs.d.ts +4 -2
  76. package/dist/content/media-refs.js +4 -2
  77. package/dist/content/media-rewrite.d.ts +8 -4
  78. package/dist/content/media-rewrite.js +76 -38
  79. package/dist/content/schema.d.ts +20 -10
  80. package/dist/content/site-dictionary.d.ts +4 -2
  81. package/dist/content/site-dictionary.js +8 -4
  82. package/dist/content/types.d.ts +97 -42
  83. package/dist/delivery/CairnHead.svelte +8 -11
  84. package/dist/delivery/content-index.d.ts +16 -8
  85. package/dist/delivery/feeds.js +4 -2
  86. package/dist/delivery/json-ld.d.ts +3 -0
  87. package/dist/delivery/json-ld.js +3 -0
  88. package/dist/delivery/manifest.d.ts +4 -2
  89. package/dist/delivery/manifest.js +4 -2
  90. package/dist/delivery/public-routes.d.ts +12 -6
  91. package/dist/delivery/public-routes.js +4 -2
  92. package/dist/delivery/seo-fields.d.ts +12 -6
  93. package/dist/delivery/seo-fields.js +8 -4
  94. package/dist/delivery/site-indexes.d.ts +4 -2
  95. package/dist/delivery/site-resolver.d.ts +4 -2
  96. package/dist/delivery/site-resolver.js +4 -2
  97. package/dist/doctor/cloudflare-api.d.ts +6 -0
  98. package/dist/doctor/cloudflare-api.js +6 -0
  99. package/dist/doctor/index.d.ts +12 -6
  100. package/dist/doctor/report.d.ts +3 -0
  101. package/dist/doctor/report.js +3 -0
  102. package/dist/doctor/run.d.ts +3 -0
  103. package/dist/doctor/run.js +3 -0
  104. package/dist/doctor/types.d.ts +10 -2
  105. package/dist/doctor/types.js +6 -0
  106. package/dist/doctor/wrangler-config.d.ts +7 -2
  107. package/dist/doctor/wrangler-config.js +3 -0
  108. package/dist/email.d.ts +4 -2
  109. package/dist/env.d.ts +0 -3
  110. package/dist/env.js +0 -3
  111. package/dist/github/branches.d.ts +4 -2
  112. package/dist/github/branches.js +4 -2
  113. package/dist/github/signing.d.ts +1 -1
  114. package/dist/github/signing.js +2 -2
  115. package/dist/log/events.d.ts +1 -1
  116. package/dist/media/bulk-delete-plan.d.ts +8 -4
  117. package/dist/media/config.d.ts +12 -6
  118. package/dist/media/config.js +16 -8
  119. package/dist/media/delivery-bucket.d.ts +4 -2
  120. package/dist/media/library-entry.d.ts +4 -2
  121. package/dist/media/library-entry.js +4 -2
  122. package/dist/media/manifest.d.ts +29 -15
  123. package/dist/media/manifest.js +29 -16
  124. package/dist/media/naming.d.ts +12 -6
  125. package/dist/media/naming.js +24 -12
  126. package/dist/media/orphan-scan.d.ts +4 -2
  127. package/dist/media/reconcile.d.ts +21 -11
  128. package/dist/media/reconcile.js +12 -6
  129. package/dist/media/reference.d.ts +8 -4
  130. package/dist/media/reference.js +12 -6
  131. package/dist/media/rewrite-plan.d.ts +12 -6
  132. package/dist/media/sniff.d.ts +4 -2
  133. package/dist/media/sniff.js +28 -14
  134. package/dist/media/store.d.ts +16 -8
  135. package/dist/media/store.js +4 -2
  136. package/dist/media/transform-url.d.ts +12 -6
  137. package/dist/media/transform-url.js +8 -4
  138. package/dist/media/usage.d.ts +8 -4
  139. package/dist/nav/site-config.d.ts +16 -8
  140. package/dist/render/component-grammar.d.ts +23 -10
  141. package/dist/render/component-grammar.js +19 -8
  142. package/dist/render/component-insert.d.ts +8 -4
  143. package/dist/render/component-insert.js +4 -2
  144. package/dist/render/component-reference.d.ts +4 -2
  145. package/dist/render/component-reference.js +4 -2
  146. package/dist/render/component-validate.d.ts +3 -0
  147. package/dist/render/component-validate.js +3 -0
  148. package/dist/render/glyph.d.ts +4 -2
  149. package/dist/render/glyph.js +4 -2
  150. package/dist/render/pipeline.d.ts +20 -10
  151. package/dist/render/pipeline.js +4 -2
  152. package/dist/render/registry.d.ts +40 -20
  153. package/dist/render/registry.js +16 -8
  154. package/dist/render/rehype-dispatch.d.ts +22 -8
  155. package/dist/render/rehype-dispatch.js +22 -8
  156. package/dist/render/remark-directives.d.ts +3 -0
  157. package/dist/render/remark-directives.js +3 -0
  158. package/dist/render/remark-figure.d.ts +4 -2
  159. package/dist/render/remark-figure.js +4 -2
  160. package/dist/render/resolve-links.d.ts +4 -2
  161. package/dist/render/resolve-links.js +4 -2
  162. package/dist/render/resolve-media.d.ts +16 -8
  163. package/dist/render/resolve-media.js +12 -6
  164. package/dist/sveltekit/admin-dispatch.d.ts +2 -0
  165. package/dist/sveltekit/admin-dispatch.js +9 -3
  166. package/dist/sveltekit/auth-routes.d.ts +3 -0
  167. package/dist/sveltekit/auth-routes.js +3 -0
  168. package/dist/sveltekit/cairn-admin.d.ts +16 -5
  169. package/dist/sveltekit/cairn-admin.js +26 -10
  170. package/dist/sveltekit/content-routes.d.ts +191 -86
  171. package/dist/sveltekit/content-routes.js +295 -107
  172. package/dist/sveltekit/editors-routes.d.ts +3 -0
  173. package/dist/sveltekit/editors-routes.js +3 -0
  174. package/dist/sveltekit/guard.d.ts +4 -2
  175. package/dist/sveltekit/guard.js +4 -2
  176. package/dist/sveltekit/https-required-page.d.ts +1 -1
  177. package/dist/sveltekit/https-required-page.js +1 -1
  178. package/dist/sveltekit/index.d.ts +1 -1
  179. package/dist/sveltekit/media-route.d.ts +1 -2
  180. package/dist/sveltekit/media-route.js +13 -8
  181. package/dist/sveltekit/nav-routes.d.ts +7 -2
  182. package/dist/sveltekit/nav-routes.js +3 -0
  183. package/dist/sveltekit/types.d.ts +4 -2
  184. package/dist/vite/index.d.ts +32 -16
  185. package/dist/vite/index.js +52 -26
  186. package/dist/vite/resolve-root.d.ts +8 -4
  187. package/dist/vite/resolve-root.js +4 -2
  188. package/package.json +8 -2
  189. package/src/lib/components/AdminLayout.svelte +22 -0
  190. package/src/lib/components/CairnAdmin.svelte +3 -0
  191. package/src/lib/components/CairnTidySettings.svelte +2 -2
  192. package/src/lib/components/ComponentForm.svelte +0 -1
  193. package/src/lib/components/EditPage.svelte +133 -41
  194. package/src/lib/components/HelpHome.svelte +850 -0
  195. package/src/lib/components/MarkdownHelpDialog.svelte +4 -15
  196. package/src/lib/components/client-ingest.ts +20 -10
  197. package/src/lib/components/editor-media.ts +20 -10
  198. package/src/lib/components/editor-placeholder.ts +12 -6
  199. package/src/lib/components/editor-tidy.ts +28 -14
  200. package/src/lib/components/index.ts +1 -0
  201. package/src/lib/components/link-completion.ts +12 -6
  202. package/src/lib/components/markdown-directives.ts +13 -8
  203. package/src/lib/components/markdown-format.ts +63 -30
  204. package/src/lib/components/markdown-reference.ts +30 -0
  205. package/src/lib/components/media-upload-outcome.ts +12 -6
  206. package/src/lib/components/objective-errors.ts +16 -8
  207. package/src/lib/components/preview-doc.ts +4 -2
  208. package/src/lib/components/spellcheck.ts +92 -40
  209. package/src/lib/components/tidy-categorize.ts +28 -14
  210. package/src/lib/components/tidy-validate.ts +28 -14
  211. package/src/lib/components/topbar-context.ts +4 -2
  212. package/src/lib/content/advisories.ts +141 -0
  213. package/src/lib/content/compose.ts +5 -2
  214. package/src/lib/content/excerpt.ts +4 -2
  215. package/src/lib/content/getting-started.ts +31 -0
  216. package/src/lib/content/links.ts +16 -8
  217. package/src/lib/content/manifest.ts +36 -18
  218. package/src/lib/content/media-refs.ts +4 -2
  219. package/src/lib/content/media-rewrite.ts +100 -50
  220. package/src/lib/content/schema.ts +20 -10
  221. package/src/lib/content/site-dictionary.ts +8 -4
  222. package/src/lib/content/types.ts +97 -42
  223. package/src/lib/delivery/content-index.ts +16 -8
  224. package/src/lib/delivery/feeds.ts +4 -2
  225. package/src/lib/delivery/json-ld.ts +3 -0
  226. package/src/lib/delivery/manifest.ts +4 -2
  227. package/src/lib/delivery/public-routes.ts +16 -8
  228. package/src/lib/delivery/seo-fields.ts +12 -6
  229. package/src/lib/delivery/site-indexes.ts +4 -2
  230. package/src/lib/delivery/site-resolver.ts +4 -2
  231. package/src/lib/doctor/cloudflare-api.ts +6 -0
  232. package/src/lib/doctor/index.ts +12 -6
  233. package/src/lib/doctor/report.ts +3 -0
  234. package/src/lib/doctor/run.ts +3 -0
  235. package/src/lib/doctor/types.ts +10 -2
  236. package/src/lib/doctor/wrangler-config.ts +7 -2
  237. package/src/lib/email.ts +4 -2
  238. package/src/lib/env.ts +0 -3
  239. package/src/lib/github/branches.ts +4 -2
  240. package/src/lib/github/signing.ts +2 -2
  241. package/src/lib/log/events.ts +1 -0
  242. package/src/lib/media/bulk-delete-plan.ts +8 -4
  243. package/src/lib/media/config.ts +24 -12
  244. package/src/lib/media/delivery-bucket.ts +4 -2
  245. package/src/lib/media/library-entry.ts +4 -2
  246. package/src/lib/media/manifest.ts +33 -18
  247. package/src/lib/media/naming.ts +24 -12
  248. package/src/lib/media/orphan-scan.ts +4 -2
  249. package/src/lib/media/reconcile.ts +21 -11
  250. package/src/lib/media/reference.ts +12 -6
  251. package/src/lib/media/rewrite-plan.ts +12 -6
  252. package/src/lib/media/sniff.ts +28 -14
  253. package/src/lib/media/store.ts +16 -8
  254. package/src/lib/media/transform-url.ts +12 -6
  255. package/src/lib/media/usage.ts +8 -4
  256. package/src/lib/nav/site-config.ts +16 -8
  257. package/src/lib/render/component-grammar.ts +23 -10
  258. package/src/lib/render/component-insert.ts +8 -4
  259. package/src/lib/render/component-reference.ts +4 -2
  260. package/src/lib/render/component-validate.ts +3 -0
  261. package/src/lib/render/glyph.ts +4 -2
  262. package/src/lib/render/pipeline.ts +20 -10
  263. package/src/lib/render/registry.ts +44 -22
  264. package/src/lib/render/rehype-dispatch.ts +22 -8
  265. package/src/lib/render/remark-directives.ts +3 -0
  266. package/src/lib/render/remark-figure.ts +4 -2
  267. package/src/lib/render/resolve-links.ts +4 -2
  268. package/src/lib/render/resolve-media.ts +16 -8
  269. package/src/lib/sveltekit/admin-dispatch.ts +10 -4
  270. package/src/lib/sveltekit/auth-routes.ts +3 -0
  271. package/src/lib/sveltekit/cairn-admin.ts +37 -15
  272. package/src/lib/sveltekit/content-routes.ts +492 -197
  273. package/src/lib/sveltekit/editors-routes.ts +3 -0
  274. package/src/lib/sveltekit/guard.ts +4 -2
  275. package/src/lib/sveltekit/https-required-page.ts +1 -1
  276. package/src/lib/sveltekit/index.ts +3 -0
  277. package/src/lib/sveltekit/media-route.ts +13 -8
  278. package/src/lib/sveltekit/nav-routes.ts +7 -2
  279. package/src/lib/sveltekit/types.ts +4 -2
  280. package/src/lib/vite/index.ts +60 -30
  281. package/src/lib/vite/resolve-root.ts +8 -4
@@ -1,14 +1,18 @@
1
1
  import type { UsageEntry, UsageIndex } from './usage.js';
2
2
  import type { MediaManifest } from './manifest.js';
3
- /** One selected hash that is not deleted, with why and (for the where-used) its usage rows. The rows
4
- * are present only for 'still-referenced'; an 'uncommitted' skip carries an empty list. */
3
+ /**
4
+ * One selected hash that is not deleted, with why and (for the where-used) its usage rows. The rows
5
+ * are present only for 'still-referenced'; an 'uncommitted' skip carries an empty list.
6
+ */
5
7
  export interface BulkDeleteSkip {
6
8
  hash: string;
7
9
  reason: 'still-referenced' | 'uncommitted';
8
10
  usage: UsageEntry[];
9
11
  }
10
- /** The partitioned selection: the hashes safe to purge and the hashes held back. Both arrays keep the
11
- * input order of `selected` so the screen reports them in the order the user picked. */
12
+ /**
13
+ * The partitioned selection: the hashes safe to purge and the hashes held back. Both arrays keep the
14
+ * input order of `selected` so the screen reports them in the order the user picked.
15
+ */
12
16
  export interface BulkDeletePlan {
13
17
  deletable: string[];
14
18
  skipped: BulkDeleteSkip[];
@@ -1,8 +1,10 @@
1
1
  import type { AssetConfig } from '../content/types.js';
2
2
  import type { VariantSpec } from './transform-url.js';
3
- /** The resolved media config the engine serves from. When a site declares no assets block, media is
3
+ /**
4
+ * The resolved media config the engine serves from. When a site declares no assets block, media is
4
5
  * off and the value is `{ enabled: false }`; otherwise every field is filled from the AssetConfig
5
- * or its default. */
6
+ * or its default.
7
+ */
6
8
  export type ResolvedAssetConfig = {
7
9
  enabled: false;
8
10
  } | {
@@ -13,12 +15,16 @@ export type ResolvedAssetConfig = {
13
15
  maxUploadBytes: number;
14
16
  allowedTypes: string[];
15
17
  variants: Record<string, VariantSpec>;
16
- /** Whether Cloudflare Image Transformations are enabled for the zone. With it false, the media
17
- * resolver serves the bare full-size delivery path and ignores any preset. */
18
+ /**
19
+ * Whether Cloudflare Image Transformations are enabled for the zone. With it false, the media
20
+ * resolver serves the bare full-size delivery path and ignores any preset.
21
+ */
18
22
  transformations: boolean;
19
23
  };
20
- /** Validate a site's AssetConfig and resolve it into a ResolvedAssetConfig. An undefined block leaves
24
+ /**
25
+ * Validate a site's AssetConfig and resolve it into a ResolvedAssetConfig. An undefined block leaves
21
26
  * media off and returns `{ enabled: false }` rather than throwing. A declared block must name its R2
22
27
  * bucket and carry a known urlForm and valid variant fit and gravity values; each failure throws a
23
- * cairn:-prefixed error. The named variants merge over the built-in presets. */
28
+ * cairn:-prefixed error. The named variants merge over the built-in presets.
29
+ */
24
30
  export declare function normalizeAssets(assets: AssetConfig | undefined): ResolvedAssetConfig;
@@ -4,8 +4,10 @@ const DEFAULT_PUBLIC_BASE = '/media';
4
4
  const DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
5
5
  /** The default accepted upload MIME types: the common web image formats. */
6
6
  const DEFAULT_ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/avif'];
7
- /** The built-in named transform presets. A site's `variants` merge over these, so a caller preset of
8
- * the same name overrides the built-in. */
7
+ /**
8
+ * The built-in named transform presets. A site's `variants` merge over these, so a caller preset of
9
+ * the same name overrides the built-in.
10
+ */
9
11
  const BUILT_IN_PRESETS = {
10
12
  thumb: { width: 320, height: 320, fit: 'cover' },
11
13
  inline: { width: 800 },
@@ -14,8 +16,10 @@ const BUILT_IN_PRESETS = {
14
16
  };
15
17
  /** The fit values Cloudflare Images accepts. A variant whose fit is set to anything else is rejected. */
16
18
  const FIT_VALUES = new Set(['scale-down', 'contain', 'cover', 'crop', 'pad']);
17
- /** The named gravity keywords Cloudflare Images accepts. A gravity is also valid as a coordinate
18
- * string; everything else is rejected. */
19
+ /**
20
+ * The named gravity keywords Cloudflare Images accepts. A gravity is also valid as a coordinate
21
+ * string; everything else is rejected.
22
+ */
19
23
  const GRAVITY_KEYWORDS = new Set([
20
24
  'auto',
21
25
  'face',
@@ -27,9 +31,11 @@ const GRAVITY_KEYWORDS = new Set([
27
31
  ]);
28
32
  /** A gravity coordinate string, e.g. "0.5x0.5". */
29
33
  const GRAVITY_COORD_RE = /^\d+(\.\d+)?x\d+(\.\d+)?$/;
30
- /** Validate one variant's fit and gravity, throwing a cairn:-prefixed error naming the offending
34
+ /**
35
+ * Validate one variant's fit and gravity, throwing a cairn:-prefixed error naming the offending
31
36
  * preset and value. The type system collapses VariantSpec.gravity to string, so the gravity check
32
- * is the only guard against a bogus value reaching the transform URL. */
37
+ * is the only guard against a bogus value reaching the transform URL.
38
+ */
33
39
  function validateVariant(name, spec) {
34
40
  if (spec.fit !== undefined && !FIT_VALUES.has(spec.fit)) {
35
41
  throw new Error(`cairn: media variant "${name}" has an unknown fit "${spec.fit}"`);
@@ -40,10 +46,12 @@ function validateVariant(name, spec) {
40
46
  throw new Error(`cairn: media variant "${name}" has an unknown gravity "${spec.gravity}"`);
41
47
  }
42
48
  }
43
- /** Validate a site's AssetConfig and resolve it into a ResolvedAssetConfig. An undefined block leaves
49
+ /**
50
+ * Validate a site's AssetConfig and resolve it into a ResolvedAssetConfig. An undefined block leaves
44
51
  * media off and returns `{ enabled: false }` rather than throwing. A declared block must name its R2
45
52
  * bucket and carry a known urlForm and valid variant fit and gravity values; each failure throws a
46
- * cairn:-prefixed error. The named variants merge over the built-in presets. */
53
+ * cairn:-prefixed error. The named variants merge over the built-in presets.
54
+ */
47
55
  export function normalizeAssets(assets) {
48
56
  if (assets === undefined)
49
57
  return { enabled: false };
@@ -6,9 +6,11 @@ export interface DeliveryObject {
6
6
  httpEtag: string;
7
7
  /** The full object size in bytes, the denominator of a `Content-Range`. */
8
8
  size: number;
9
- /** Present only on a ranged read: the served window, used to build the `Content-Range`. R2 fills
9
+ /**
10
+ * Present only on a ranged read: the served window, used to build the `Content-Range`. R2 fills
10
11
  * both fields for a `bytes=start-end` request; each is typed optional so the route derives the
11
- * range bounds defensively against `size`. */
12
+ * range bounds defensively against `size`.
13
+ */
12
14
  range?: {
13
15
  offset?: number;
14
16
  length?: number;
@@ -24,7 +24,9 @@ export interface MediaLibraryEntry {
24
24
  }
25
25
  /** The projected library keyed by the 16-hex content hash, exactly EditData's `mediaLibrary`. */
26
26
  export type MediaLibrary = Record<string, MediaLibraryEntry>;
27
- /** Project a stored MediaEntry to the picker's MediaLibraryEntry, copying every display field and
27
+ /**
28
+ * Project a stored MediaEntry to the picker's MediaLibraryEntry, copying every display field and
28
29
  * dropping the source-only sha256 and original filename. The single projection editLoad and
29
- * mediaLibraryLoad both call, so the popover and the Library never diverge on the shared shape. */
30
+ * mediaLibraryLoad both call, so the popover and the Library never diverge on the shared shape.
31
+ */
30
32
  export declare function mediaLibraryEntry(entry: MediaEntry): MediaLibraryEntry;
@@ -1,6 +1,8 @@
1
- /** Project a stored MediaEntry to the picker's MediaLibraryEntry, copying every display field and
1
+ /**
2
+ * Project a stored MediaEntry to the picker's MediaLibraryEntry, copying every display field and
2
3
  * dropping the source-only sha256 and original filename. The single projection editLoad and
3
- * mediaLibraryLoad both call, so the popover and the Library never diverge on the shared shape. */
4
+ * mediaLibraryLoad both call, so the popover and the Library never diverge on the shared shape.
5
+ */
4
6
  export function mediaLibraryEntry(entry) {
5
7
  return {
6
8
  hash: entry.hash,
@@ -1,7 +1,9 @@
1
- /** One stored asset's row: its content hash, its human layer, and its byte and pixel facts. The
1
+ /**
2
+ * One stored asset's row: its content hash, its human layer, and its byte and pixel facts. The
2
3
  * `contentType` is the stored MIME type, so the delivery route serves it verbatim rather than
3
4
  * guessing from the extension. `width` and `height` are null when no dimensions are known (the
4
- * client is the only dimension source and a Worker cannot re-derive them). */
5
+ * client is the only dimension source and a Worker cannot re-derive them).
6
+ */
5
7
  export interface MediaEntry {
6
8
  hash: string;
7
9
  sha256: string;
@@ -18,27 +20,39 @@ export interface MediaEntry {
18
20
  }
19
21
  /** The whole stored-asset record, keyed by the 16-hex content-hash prefix. */
20
22
  export type MediaManifest = Record<string, MediaEntry>;
21
- /** Parse a committed media manifest. Tolerant: an empty, missing, null, or non-object input yields
22
- * an empty manifest, so a first ingest into a site with no manifest file reads a clean {}. A valid
23
- * object is returned as the manifest. */
23
+ /**
24
+ * Parse a committed media manifest. Tolerant: an empty, missing, null, or non-object input yields
25
+ * an empty manifest, so a first ingest into a site with no manifest file reads a clean `{}`. A valid
26
+ * object is returned as the manifest.
27
+ */
24
28
  export declare function parseMediaManifest(json: unknown): MediaManifest;
25
- /** Parse the posted `media` field into a validated list of MediaEntry rows. The field arrives as a
29
+ /**
30
+ * Parse the posted `media` field into a validated list of MediaEntry rows. The field arrives as a
26
31
  * JSON string (the usual form-post shape), an already-parsed array, or junk. A string is JSON-parsed
27
32
  * inside a try/catch that yields `[]` on a parse failure; a non-string array is taken directly;
28
33
  * anything else yields `[]`. Each element is validated and a failing element is dropped, so a partly
29
34
  * malformed post still lands its good rows. This is the trust boundary for the client's optimistic
30
- * records. */
35
+ * records.
36
+ */
31
37
  export declare function parseMediaEntries(value: unknown): MediaEntry[];
32
- /** The dedup lookup: the entry stored under the content-hash prefix, or undefined when no bytes with
33
- * that hash are stored yet. */
38
+ /**
39
+ * The dedup lookup: the entry stored under the content-hash prefix, or undefined when no bytes with
40
+ * that hash are stored yet.
41
+ */
34
42
  export declare function findByHash(manifest: MediaManifest, hash: string): MediaEntry | undefined;
35
- /** Set the entry under its own hash, replacing any same-hash row. Returns a new manifest and leaves
36
- * the input untouched, so a caller's prior manifest reference stays valid. The ingest path's patch. */
43
+ /**
44
+ * Set the entry under its own hash, replacing any same-hash row. Returns a new manifest and leaves
45
+ * the input untouched, so a caller's prior manifest reference stays valid. The ingest path's patch.
46
+ */
37
47
  export declare function upsertMediaEntry(manifest: MediaManifest, entry: MediaEntry): MediaManifest;
38
- /** Drop the entry under the given hash, returning a new manifest and leaving the input untouched.
48
+ /**
49
+ * Drop the entry under the given hash, returning a new manifest and leaving the input untouched.
39
50
  * Removing an absent hash is a no-op that still returns an equivalent new manifest. The safe-delete
40
- * path's patch. */
51
+ * path's patch.
52
+ */
41
53
  export declare function removeMediaEntry(manifest: MediaManifest, hash: string): MediaManifest;
42
- /** Serialize canonically: the top-level hash keys sorted ascending, two-space pretty, and a trailing
43
- * newline, so the committed file diffs cleanly in a PR and a re-serialization is byte-identical. */
54
+ /**
55
+ * Serialize canonically: the top-level hash keys sorted ascending, two-space pretty, and a trailing
56
+ * newline, so the committed file diffs cleanly in a PR and a re-serialization is byte-identical.
57
+ */
44
58
  export declare function serializeMediaManifest(manifest: MediaManifest): string;
@@ -3,19 +3,22 @@
3
3
  // the dedup lookup: an ingest checks the content-hash prefix here before storing, so the same bytes
4
4
  // are never stored twice. It mirrors the content manifest in ../content/manifest.ts, keyed by the
5
5
  // 16-hex content-hash prefix rather than concept and id.
6
- /** Parse a committed media manifest. Tolerant: an empty, missing, null, or non-object input yields
7
- * an empty manifest, so a first ingest into a site with no manifest file reads a clean {}. A valid
8
- * object is returned as the manifest. */
6
+ /**
7
+ * Parse a committed media manifest. Tolerant: an empty, missing, null, or non-object input yields
8
+ * an empty manifest, so a first ingest into a site with no manifest file reads a clean `{}`. A valid
9
+ * object is returned as the manifest.
10
+ */
9
11
  export function parseMediaManifest(json) {
10
12
  if (!json || typeof json !== 'object' || Array.isArray(json))
11
13
  return {};
12
14
  return json;
13
15
  }
14
- /** Validate one posted value as a MediaEntry, returning it narrowed or undefined. The trust boundary
16
+ /**
17
+ * Validate one posted value as a MediaEntry, returning it narrowed or undefined. The trust boundary
15
18
  * for an optimistic record the client re-posts: the upload action server-owned each field at
16
19
  * creation, but a re-post is untrusted, so every field is re-checked. A `hash` must be the 16-hex
17
- * content-hash prefix; the string fields must be strings; `bytes` must be finite; `width`/`height`
18
- * must each be a number or null; `createdAt` must be a string. */
20
+ * content-hash prefix.
21
+ */
19
22
  function validateMediaEntry(value) {
20
23
  if (!value || typeof value !== 'object')
21
24
  return undefined;
@@ -51,12 +54,14 @@ function validateMediaEntry(value) {
51
54
  createdAt: e.createdAt,
52
55
  };
53
56
  }
54
- /** Parse the posted `media` field into a validated list of MediaEntry rows. The field arrives as a
57
+ /**
58
+ * Parse the posted `media` field into a validated list of MediaEntry rows. The field arrives as a
55
59
  * JSON string (the usual form-post shape), an already-parsed array, or junk. A string is JSON-parsed
56
60
  * inside a try/catch that yields `[]` on a parse failure; a non-string array is taken directly;
57
61
  * anything else yields `[]`. Each element is validated and a failing element is dropped, so a partly
58
62
  * malformed post still lands its good rows. This is the trust boundary for the client's optimistic
59
- * records. */
63
+ * records.
64
+ */
60
65
  export function parseMediaEntries(value) {
61
66
  let raw = value;
62
67
  if (typeof value === 'string') {
@@ -77,25 +82,33 @@ export function parseMediaEntries(value) {
77
82
  }
78
83
  return entries;
79
84
  }
80
- /** The dedup lookup: the entry stored under the content-hash prefix, or undefined when no bytes with
81
- * that hash are stored yet. */
85
+ /**
86
+ * The dedup lookup: the entry stored under the content-hash prefix, or undefined when no bytes with
87
+ * that hash are stored yet.
88
+ */
82
89
  export function findByHash(manifest, hash) {
83
90
  return manifest[hash];
84
91
  }
85
- /** Set the entry under its own hash, replacing any same-hash row. Returns a new manifest and leaves
86
- * the input untouched, so a caller's prior manifest reference stays valid. The ingest path's patch. */
92
+ /**
93
+ * Set the entry under its own hash, replacing any same-hash row. Returns a new manifest and leaves
94
+ * the input untouched, so a caller's prior manifest reference stays valid. The ingest path's patch.
95
+ */
87
96
  export function upsertMediaEntry(manifest, entry) {
88
97
  return { ...manifest, [entry.hash]: entry };
89
98
  }
90
- /** Drop the entry under the given hash, returning a new manifest and leaving the input untouched.
99
+ /**
100
+ * Drop the entry under the given hash, returning a new manifest and leaving the input untouched.
91
101
  * Removing an absent hash is a no-op that still returns an equivalent new manifest. The safe-delete
92
- * path's patch. */
102
+ * path's patch.
103
+ */
93
104
  export function removeMediaEntry(manifest, hash) {
94
105
  const { [hash]: _removed, ...rest } = manifest;
95
106
  return rest;
96
107
  }
97
- /** Serialize canonically: the top-level hash keys sorted ascending, two-space pretty, and a trailing
98
- * newline, so the committed file diffs cleanly in a PR and a re-serialization is byte-identical. */
108
+ /**
109
+ * Serialize canonically: the top-level hash keys sorted ascending, two-space pretty, and a trailing
110
+ * newline, so the committed file diffs cleanly in a PR and a re-serialization is byte-identical.
111
+ */
99
112
  export function serializeMediaManifest(manifest) {
100
113
  const sorted = {};
101
114
  for (const hash of Object.keys(manifest).sort()) {
@@ -2,17 +2,23 @@
2
2
  export declare function hashBytes(bytes: Uint8Array): Promise<string>;
3
3
  /** The first 16 characters of a full hex digest, the content-hash prefix media references commit to. */
4
4
  export declare function shortHash(full: string): string;
5
- /** The strict ingest transform from a raw filename to a slug that satisfies the media: slug grammar,
5
+ /**
6
+ * The strict ingest transform from a raw filename to a slug that satisfies the media: slug grammar,
6
7
  * or the literal `file`. Drops the extension, lowercases, transliterates accents, collapses non-alphanumeric runs
7
8
  * to a single hyphen, trims, caps at 80 chars, screens Windows reserved names, and falls back to
8
- * `file` when nothing usable is left. */
9
+ * `file` when nothing usable is left.
10
+ */
9
11
  export declare function slugifyFilename(name: string): string;
10
- /** The content-addressed R2 object key `media/<aa>/<shortHash>.<ext>`, fanned out on the first two
12
+ /**
13
+ * The content-addressed R2 object key `media/<aa>/<shortHash>.<ext>`, fanned out on the first two
11
14
  * hex chars of the short hash. No leading slash: this is an object key, not a URL. `ext` is bare
12
- * (no dot), for example `webp`. */
15
+ * (no dot), for example `webp`.
16
+ */
13
17
  export declare function r2Key(shortHash: string, ext: string): string;
14
- /** The public delivery URL path, with a leading slash, under the delivery base (`publicBase`,
18
+ /**
19
+ * The public delivery URL path, with a leading slash, under the delivery base (`publicBase`,
15
20
  * default `/media`). The `slug` form is human-readable (`<base>/<slug>.<shortHash>.<ext>`, or
16
21
  * `<base>/<shortHash>.<ext>` when the slug is null); the `opaque` form mirrors the R2 fan-out
17
- * (`<base>/<aa>/<shortHash>.<ext>`) and ignores the slug. */
22
+ * (`<base>/<aa>/<shortHash>.<ext>`) and ignores the slug.
23
+ */
18
24
  export declare function publicPath(slug: string | null, shortHash: string, ext: string, urlForm: 'slug' | 'opaque', publicBase?: string): string;
@@ -5,12 +5,16 @@
5
5
  // trips through the media: token unchanged.
6
6
  // slugifyFilename output always satisfies parseMediaToken's grammar (lowercase alphanumerics joined
7
7
  // by single internal hyphens, no leading or trailing hyphen), or is the literal `file`.
8
- /** Combining marks (Unicode block U+0300 to U+036F), left over after an NFD decompose, stripped to
8
+ /**
9
+ * Combining marks (Unicode block U+0300 to U+036F), left over after an NFD decompose, stripped to
9
10
  * fold an accented letter down to its ASCII base. Written as escapes because the literal marks are
10
- * invisible in source. */
11
+ * invisible in source.
12
+ */
11
13
  const COMBINING_MARKS = /[\u0300-\u036f]/g;
12
- /** Windows reserved device names. A bare match (case-insensitive) cannot survive as the slug, since
13
- * it names a device rather than a file on that platform. */
14
+ /**
15
+ * Windows reserved device names. A bare match (case-insensitive) cannot survive as the slug, since
16
+ * it names a device rather than a file on that platform.
17
+ */
14
18
  const RESERVED = new Set([
15
19
  'con',
16
20
  'prn',
@@ -37,8 +41,10 @@ const RESERVED = new Set([
37
41
  ]);
38
42
  /** The maximum slug length, applied before the reserved-name and empty fallbacks. */
39
43
  const MAX_SLUG = 80;
40
- /** A 16-character lowercase hex content-hash prefix, the bare-hash reference form. A slug that
41
- * matches this shape would collide with `media:<hash>`, so slugifyFilename screens it. */
44
+ /**
45
+ * A 16-character lowercase hex content-hash prefix, the bare-hash reference form. A slug that
46
+ * matches this shape would collide with `media:<hash>`, so slugifyFilename screens it.
47
+ */
42
48
  const HASH_RE = /^[0-9a-f]{16}$/;
43
49
  /** A short alphanumeric extension (no dot), the only shape r2Key accepts, for example `webp`. */
44
50
  const R2_EXT_RE = /^[a-z0-9]{1,5}$/;
@@ -59,10 +65,12 @@ export async function hashBytes(bytes) {
59
65
  export function shortHash(full) {
60
66
  return full.slice(0, 16);
61
67
  }
62
- /** The strict ingest transform from a raw filename to a slug that satisfies the media: slug grammar,
68
+ /**
69
+ * The strict ingest transform from a raw filename to a slug that satisfies the media: slug grammar,
63
70
  * or the literal `file`. Drops the extension, lowercases, transliterates accents, collapses non-alphanumeric runs
64
71
  * to a single hyphen, trims, caps at 80 chars, screens Windows reserved names, and falls back to
65
- * `file` when nothing usable is left. */
72
+ * `file` when nothing usable is left.
73
+ */
66
74
  export function slugifyFilename(name) {
67
75
  const dot = name.lastIndexOf('.');
68
76
  const stem = dot === -1 ? name : name.slice(0, dot);
@@ -86,9 +94,11 @@ export function slugifyFilename(name) {
86
94
  return `${slug}-img`;
87
95
  return slug;
88
96
  }
89
- /** The content-addressed R2 object key `media/<aa>/<shortHash>.<ext>`, fanned out on the first two
97
+ /**
98
+ * The content-addressed R2 object key `media/<aa>/<shortHash>.<ext>`, fanned out on the first two
90
99
  * hex chars of the short hash. No leading slash: this is an object key, not a URL. `ext` is bare
91
- * (no dot), for example `webp`. */
100
+ * (no dot), for example `webp`.
101
+ */
92
102
  export function r2Key(shortHash, ext) {
93
103
  if (!HASH_RE.test(shortHash)) {
94
104
  throw new Error(`r2Key: hash must be 16 lowercase hex chars, got "${shortHash}"`);
@@ -98,10 +108,12 @@ export function r2Key(shortHash, ext) {
98
108
  }
99
109
  return `media/${shortHash.slice(0, 2)}/${shortHash}.${ext}`;
100
110
  }
101
- /** The public delivery URL path, with a leading slash, under the delivery base (`publicBase`,
111
+ /**
112
+ * The public delivery URL path, with a leading slash, under the delivery base (`publicBase`,
102
113
  * default `/media`). The `slug` form is human-readable (`<base>/<slug>.<shortHash>.<ext>`, or
103
114
  * `<base>/<shortHash>.<ext>` when the slug is null); the `opaque` form mirrors the R2 fan-out
104
- * (`<base>/<aa>/<shortHash>.<ext>`) and ignores the slug. */
115
+ * (`<base>/<aa>/<shortHash>.<ext>`) and ignores the slug.
116
+ */
105
117
  export function publicPath(slug, shortHash, ext, urlForm, publicBase = '/media') {
106
118
  if (urlForm === 'opaque') {
107
119
  return `${publicBase}/${shortHash.slice(0, 2)}/${shortHash}.${ext}`;
@@ -8,8 +8,10 @@ export interface OrphanByteRow {
8
8
  /** The 16-hex content hash parsed from the key. */
9
9
  hash: string;
10
10
  }
11
- /** A broken reference: a manifest row whose bytes are gone. Read-only, since purging it would drop a
12
- * still-referenced asset's record; the screen shows where it is used so an operator can re-ingest. */
11
+ /**
12
+ * A broken reference: a manifest row whose bytes are gone. Read-only, since purging it would drop a
13
+ * still-referenced asset's record; the screen shows where it is used so an operator can re-ingest.
14
+ */
13
15
  export interface BrokenRefRow {
14
16
  /** The 16-hex content hash of the manifest row whose bytes are missing. */
15
17
  hash: string;
@@ -1,18 +1,24 @@
1
1
  import type { MediaManifest } from './manifest.js';
2
- /** A stored media object key parses to its short hash via `media/<aa>/<shortHash>.<ext>`. Exported so
3
- * the orphan-scan projection derives the same hash from an orphaned key without a second grammar. */
2
+ /**
3
+ * A stored media object key parses to its short hash via `media/<aa>/<shortHash>.<ext>`. Exported so
4
+ * the orphan-scan projection derives the same hash from an orphaned key without a second grammar.
5
+ */
4
6
  export declare const MEDIA_KEY_RE: RegExp;
5
- /** What a reconcile read found in either direction. `orphanedObjects` are stored R2 keys whose hash
6
- * has no manifest row; `missingObjects` are manifest hashes with no stored object. */
7
+ /**
8
+ * What a reconcile read found in either direction. `orphanedObjects` are stored R2 keys whose hash
9
+ * has no manifest row; `missingObjects` are manifest hashes with no stored object.
10
+ */
7
11
  export interface ReconcileResult {
8
12
  /** Stored keys (full R2 keys) whose content hash is absent from the manifest. */
9
13
  orphanedObjects: string[];
10
14
  /** Manifest content-hash keys with no matching stored object. */
11
15
  missingObjects: string[];
12
16
  }
13
- /** The pure core: compare the stored R2 keys against the manifest's content-hash keys and report
17
+ /**
18
+ * The pure core: compare the stored R2 keys against the manifest's content-hash keys and report
14
19
  * both orphan directions. A stored key that does not match the media-key grammar is ignored, since
15
- * it is not a content-addressed media object this reconcile owns. */
20
+ * it is not a content-addressed media object this reconcile owns.
21
+ */
16
22
  export declare function reconcileMedia(storedKeys: string[], manifest: MediaManifest): ReconcileResult;
17
23
  /** One page of an R2 list, the narrow subset the reconcile read consumes. */
18
24
  interface ReconcileListPage {
@@ -22,18 +28,22 @@ interface ReconcileListPage {
22
28
  truncated: boolean;
23
29
  cursor?: string;
24
30
  }
25
- /** The R2 bucket surface the reconcile read needs: a single prefixed, paginated list. A local
26
- * structural interface so no @cloudflare/workers-types name is imported (the module is internal and
27
- * on no public subpath, but the narrow seam keeps the build self-contained either way). */
31
+ /**
32
+ * The R2 bucket surface the reconcile read needs: a single prefixed, paginated list. A local
33
+ * structural interface so no `@cloudflare/workers-types` name is imported (the module is internal and
34
+ * on no public subpath, but the narrow seam keeps the build self-contained either way).
35
+ */
28
36
  export interface ReconcileBucket {
29
37
  list(opts?: {
30
38
  prefix?: string;
31
39
  cursor?: string;
32
40
  }): Promise<ReconcileListPage>;
33
41
  }
34
- /** The glue runner: list every stored key under the media/ prefix (paginating through R2's
42
+ /**
43
+ * The glue runner: list every stored key under the media/ prefix (paginating through R2's
35
44
  * cursor/truncated), reconcile against the manifest, log the count summary, and return the result.
36
45
  * The log record carries counts only, never bytes or a key list; the keys are content hashes and so
37
- * carry no PII, but the count summary is all an operator needs to size the orphan state. */
46
+ * carry no PII, but the count summary is all an operator needs to size the orphan state.
47
+ */
38
48
  export declare function runReconcile(bucket: ReconcileBucket, manifest: MediaManifest): Promise<ReconcileResult>;
39
49
  export {};
@@ -1,10 +1,14 @@
1
1
  import { log } from '../log/index.js';
2
- /** A stored media object key parses to its short hash via `media/<aa>/<shortHash>.<ext>`. Exported so
3
- * the orphan-scan projection derives the same hash from an orphaned key without a second grammar. */
2
+ /**
3
+ * A stored media object key parses to its short hash via `media/<aa>/<shortHash>.<ext>`. Exported so
4
+ * the orphan-scan projection derives the same hash from an orphaned key without a second grammar.
5
+ */
4
6
  export const MEDIA_KEY_RE = /^media\/[0-9a-f]{2}\/([0-9a-f]{16})\.[a-z0-9]{1,5}$/;
5
- /** The pure core: compare the stored R2 keys against the manifest's content-hash keys and report
7
+ /**
8
+ * The pure core: compare the stored R2 keys against the manifest's content-hash keys and report
6
9
  * both orphan directions. A stored key that does not match the media-key grammar is ignored, since
7
- * it is not a content-addressed media object this reconcile owns. */
10
+ * it is not a content-addressed media object this reconcile owns.
11
+ */
8
12
  export function reconcileMedia(storedKeys, manifest) {
9
13
  const manifestHashes = new Set(Object.keys(manifest));
10
14
  const storedHashes = new Set();
@@ -24,10 +28,12 @@ export function reconcileMedia(storedKeys, manifest) {
24
28
  }
25
29
  return { orphanedObjects, missingObjects };
26
30
  }
27
- /** The glue runner: list every stored key under the media/ prefix (paginating through R2's
31
+ /**
32
+ * The glue runner: list every stored key under the media/ prefix (paginating through R2's
28
33
  * cursor/truncated), reconcile against the manifest, log the count summary, and return the result.
29
34
  * The log record carries counts only, never bytes or a key list; the keys are content hashes and so
30
- * carry no PII, but the count summary is all an operator needs to size the orphan state. */
35
+ * carry no PII, but the count summary is all an operator needs to size the orphan state.
36
+ */
31
37
  export async function runReconcile(bucket, manifest) {
32
38
  const storedKeys = [];
33
39
  let cursor;
@@ -3,10 +3,14 @@ export interface MediaRef {
3
3
  slug: string | null;
4
4
  hash: string;
5
5
  }
6
- /** Parse a `media:<slug>.<hash>` href (or the bare `media:<hash>` form), or null for any other
6
+ /**
7
+ * Parse a `media:<slug>.<hash>` href (or the bare `media:<hash>` form), or null for any other
7
8
  * href or a malformed token. Splits on the last dot, so a slug that illegally contains a dot fails
8
- * the slug grammar and returns null. */
9
+ * the slug grammar and returns null.
10
+ */
9
11
  export declare function parseMediaToken(href: string): MediaRef | null;
10
- /** Write the canonical media: token for a ref. The inverse of parseMediaToken, so a parse then
11
- * write round trip is stable: `media:<slug>.<hash>` when the slug is present, else `media:<hash>`. */
12
+ /**
13
+ * Write the canonical media: token for a ref. The inverse of parseMediaToken, so a parse then
14
+ * write round trip is stable: `media:<slug>.<hash>` when the slug is present, else `media:<hash>`.
15
+ */
12
16
  export declare function mediaToken(ref: MediaRef): string;
@@ -6,13 +6,17 @@
6
6
  // the grammar; it mirrors the cairn: link codec in ../content/links.ts.
7
7
  /** A 16-character lowercase hex content-hash prefix. */
8
8
  const HASH_RE = /^[0-9a-f]{16}$/;
9
- /** The slug grammar from the Task 2 slugify transform: lowercase alphanumerics joined by single
9
+ /**
10
+ * The slug grammar from the Task 2 slugify transform: lowercase alphanumerics joined by single
10
11
  * internal hyphens, with no leading or trailing hyphen and no dot (the dot is the slug/hash
11
- * separator). */
12
+ * separator).
13
+ */
12
14
  const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
13
- /** Parse a `media:<slug>.<hash>` href (or the bare `media:<hash>` form), or null for any other
15
+ /**
16
+ * Parse a `media:<slug>.<hash>` href (or the bare `media:<hash>` form), or null for any other
14
17
  * href or a malformed token. Splits on the last dot, so a slug that illegally contains a dot fails
15
- * the slug grammar and returns null. */
18
+ * the slug grammar and returns null.
19
+ */
16
20
  export function parseMediaToken(href) {
17
21
  if (!href.startsWith('media:'))
18
22
  return null;
@@ -26,8 +30,10 @@ export function parseMediaToken(href) {
26
30
  return null;
27
31
  return { slug, hash };
28
32
  }
29
- /** Write the canonical media: token for a ref. The inverse of parseMediaToken, so a parse then
30
- * write round trip is stable: `media:<slug>.<hash>` when the slug is present, else `media:<hash>`. */
33
+ /**
34
+ * Write the canonical media: token for a ref. The inverse of parseMediaToken, so a parse then
35
+ * write round trip is stable: `media:<slug>.<hash>` when the slug is present, else `media:<hash>`.
36
+ */
31
37
  export function mediaToken(ref) {
32
38
  return ref.slug === null ? `media:${ref.hash}` : `media:${ref.slug}.${ref.hash}`;
33
39
  }
@@ -1,9 +1,11 @@
1
1
  import type { ConceptDescriptor } from '../content/types.js';
2
2
  import type { RepoRef } from '../github/types.js';
3
3
  import type { Manifest } from '../content/manifest.js';
4
- /** One main entry the rewrite will touch: its identity, its file path, the transform's per-placement
4
+ /**
5
+ * One main entry the rewrite will touch: its identity, its file path, the transform's per-placement
5
6
  * diff, and the rewritten markdown a later apply commits. `P` is the transform's placement type
6
- * (a RepointPlacement for replace, an AltPlacement for fill-alt). */
7
+ * (a RepointPlacement for replace, an AltPlacement for fill-alt).
8
+ */
7
9
  export interface PlannedEntry<P = unknown> {
8
10
  /** The concept id, e.g. "posts". */
9
11
  concept: string;
@@ -16,9 +18,11 @@ export interface PlannedEntry<P = unknown> {
16
18
  /** The entry's markdown after the transform, byte-identical to the source apart from the rewrite. */
17
19
  newMarkdown: string;
18
20
  }
19
- /** One open edit branch that also references the asset, with the entries on it. Report-only: an apply
21
+ /**
22
+ * One open edit branch that also references the asset, with the entries on it. Report-only: an apply
20
23
  * rewrites main, never a branch, so the screen surfaces these as a delta the editor handles by
21
- * republishing the draft. */
24
+ * republishing the draft.
25
+ */
22
26
  export interface BranchRef {
23
27
  /** The cairn/* branch name. */
24
28
  branch: string;
@@ -28,8 +32,10 @@ export interface BranchRef {
28
32
  id: string;
29
33
  }[];
30
34
  }
31
- /** The preview plan: the main entries to rewrite, the report-only branch delta, and the distinct
32
- * count of affected main entries (the entries the transform actually changed). */
35
+ /**
36
+ * The preview plan: the main entries to rewrite, the report-only branch delta, and the distinct
37
+ * count of affected main entries (the entries the transform actually changed).
38
+ */
33
39
  export interface RewritePlan<P = unknown> {
34
40
  entries: PlannedEntry<P>[];
35
41
  branchDelta: BranchRef[];