@caelo-cms/shared 0.10.52 → 0.10.55

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 (91) hide show
  1. package/dist/auto-update.d.ts +5 -2
  2. package/dist/auto-update.d.ts.map +1 -1
  3. package/dist/auto-update.js +13 -6
  4. package/dist/auto-update.js.map +1 -1
  5. package/dist/fonts.d.ts +12 -0
  6. package/dist/fonts.d.ts.map +1 -1
  7. package/dist/fonts.js +37 -1
  8. package/dist/fonts.js.map +1 -1
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/link-refs.d.ts +250 -0
  14. package/dist/link-refs.d.ts.map +1 -0
  15. package/dist/link-refs.js +416 -0
  16. package/dist/link-refs.js.map +1 -0
  17. package/dist/markup-structure.d.ts +79 -0
  18. package/dist/markup-structure.d.ts.map +1 -0
  19. package/dist/markup-structure.js +274 -0
  20. package/dist/markup-structure.js.map +1 -0
  21. package/dist/media.d.ts +25 -1
  22. package/dist/media.d.ts.map +1 -1
  23. package/dist/media.js +35 -0
  24. package/dist/media.js.map +1 -1
  25. package/dist/nested-module-render.d.ts +4 -1
  26. package/dist/nested-module-render.d.ts.map +1 -1
  27. package/dist/nested-module-render.js +14 -2
  28. package/dist/nested-module-render.js.map +1 -1
  29. package/dist/platform-updates.d.ts +7 -3
  30. package/dist/platform-updates.d.ts.map +1 -1
  31. package/dist/platform-updates.js +13 -2
  32. package/dist/platform-updates.js.map +1 -1
  33. package/dist/preview-compose.d.ts +14 -3
  34. package/dist/preview-compose.d.ts.map +1 -1
  35. package/dist/preview-compose.js +60 -21
  36. package/dist/preview-compose.js.map +1 -1
  37. package/dist/publish-live-parity.d.ts +18 -0
  38. package/dist/publish-live-parity.d.ts.map +1 -0
  39. package/dist/publish-live-parity.js +21 -0
  40. package/dist/publish-live-parity.js.map +1 -0
  41. package/dist/raw-text-escape.d.ts +18 -0
  42. package/dist/raw-text-escape.d.ts.map +1 -0
  43. package/dist/raw-text-escape.js +40 -0
  44. package/dist/raw-text-escape.js.map +1 -0
  45. package/dist/responsive-images.d.ts.map +1 -1
  46. package/dist/responsive-images.js +3 -3
  47. package/dist/responsive-images.js.map +1 -1
  48. package/dist/seo.js +1 -1
  49. package/dist/seo.js.map +1 -1
  50. package/dist/template-balance.d.ts +3 -0
  51. package/dist/template-balance.d.ts.map +1 -0
  52. package/dist/template-balance.js +214 -0
  53. package/dist/template-balance.js.map +1 -0
  54. package/dist/template-engine.d.ts +11 -0
  55. package/dist/template-engine.d.ts.map +1 -1
  56. package/dist/template-engine.js +12 -0
  57. package/dist/template-engine.js.map +1 -1
  58. package/dist/theme-render.d.ts +2 -2
  59. package/dist/theme-render.d.ts.map +1 -1
  60. package/dist/theme-render.js +29 -5
  61. package/dist/theme-render.js.map +1 -1
  62. package/dist/version.d.ts +2 -2
  63. package/dist/version.js +1 -1
  64. package/package.json +2 -2
  65. package/src/auto-update.test.ts +32 -1
  66. package/src/auto-update.ts +13 -6
  67. package/src/fonts.test.ts +37 -0
  68. package/src/fonts.ts +38 -1
  69. package/src/index.ts +4 -0
  70. package/src/link-refs.test.ts +552 -0
  71. package/src/link-refs.ts +569 -0
  72. package/src/markup-structure.test.ts +184 -0
  73. package/src/markup-structure.ts +304 -0
  74. package/src/media.ts +43 -0
  75. package/src/nested-module-render.ts +18 -1
  76. package/src/platform-updates.test.ts +5 -0
  77. package/src/platform-updates.ts +13 -2
  78. package/src/preview-compose-deferrals.test.ts +103 -0
  79. package/src/preview-compose.ts +79 -28
  80. package/src/publish-live-parity.ts +25 -0
  81. package/src/raw-text-escape.ts +44 -0
  82. package/src/responsive-images.test.ts +10 -0
  83. package/src/responsive-images.ts +6 -3
  84. package/src/seo.test.ts +7 -0
  85. package/src/seo.ts +1 -1
  86. package/src/template-balance.test.ts +97 -0
  87. package/src/template-balance.ts +216 -0
  88. package/src/template-engine.ts +23 -0
  89. package/src/theme-render-sanitize.test.ts +40 -0
  90. package/src/theme-render.ts +40 -8
  91. package/src/version.ts +1 -1
@@ -0,0 +1,416 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * Internal links as REFERENCES, not literal paths (epic #724).
4
+ *
5
+ * A link to one of the site's own pages or media assets is stored as a
6
+ * plain-string reference and turned into a URL only when a page is
7
+ * rendered — for the page being rendered, in the environment it is
8
+ * rendered for. Renaming or moving a page then changes no stored link.
9
+ *
10
+ * Grammar (one form for every place a link can live — `link` / `url`
11
+ * fields, `link-list` items, `*_href` values, module HTML attributes,
12
+ * rich text, structured-set items, field defaults):
13
+ *
14
+ * caelo:page/<pageId>[?query][#anchor]
15
+ * caelo:media/<mediaId>[/<variant>]
16
+ *
17
+ * `<pageId>` / `<mediaId>` are lowercase UUIDs. `query` and `anchor` are
18
+ * carried verbatim (`?ref=nav`, `#pricing`). `variant` is a media
19
+ * variant tag (`webp-800`, `square-400`); absent means the original.
20
+ * Because a reference is a plain string, every surface carries it
21
+ * without a schema change, and a reference inside HTML (`href="…"`,
22
+ * `src="…"`, a `srcset` entry) is found by the same scanner as a whole
23
+ * field value.
24
+ *
25
+ * Who sees the grammar: nobody who authors content. Humans and the AI
26
+ * keep writing `/about`; the write path canonicalises it to a reference
27
+ * (rollout C — until then nothing writes references), and every read
28
+ * that shows content back (tool results, editor fields) turns references
29
+ * into human paths again ({@link humaniseLinkRefs}). The grammar is a
30
+ * storage form, not an authoring form (CLAUDE.md §1A).
31
+ *
32
+ * Resolution is ONE interface ({@link LinkResolver}) used by the
33
+ * template engine, the composer, the editor preview, live-edit and the
34
+ * static generator. A reference whose target does not exist is a loud
35
+ * failure (`link-target-missing:<kind>/<id>` in `missingSlots`, the raw
36
+ * reference left in the output — CLAUDE.md §2); one whose target exists
37
+ * but is not published in the environment being rendered resolves to
38
+ * its URL and is reported as `unpublished` so the caller can apply the
39
+ * environment's policy (warn at Stage, block at Publish live — the
40
+ * blocking check is rollout F).
41
+ *
42
+ * Seams for the later rollout steps:
43
+ * - D (locale composition point): {@link SiteLinkIndex.forPage} is the
44
+ * per-rendering-page hook. A `link-resolve` contribution maps the
45
+ * target to the rendering page's same-locale sibling there; nothing
46
+ * in the engine or composer changes.
47
+ * - E (data migration): {@link mapLinkRefsDeep} / the canonicaliser's
48
+ * pure core walk the same shapes the migration rewrites.
49
+ * - F (reference integrity): {@link RecordingLinkResolver.observed}
50
+ * lists every reference a render touched with its resolution, and
51
+ * {@link linkIssueSeverity} is the per-environment policy.
52
+ */
53
+ import { buildMediaUrl } from "./media.js";
54
+ import { trimSlashes } from "./url.js";
55
+ /** The scheme every internal reference starts with. */
56
+ export const LINK_REF_SCHEME = "caelo:";
57
+ /**
58
+ * `missingSlots` marker for a link value that starts like a reference
59
+ * (`caelo:page/`, `caelo:media/`) but does not match the grammar — a
60
+ * truncated or uppercased id, an id followed by stray characters
61
+ * (`caelo:page/<id>/team`), an unknown media variant form. Reported as
62
+ * `link-ref-malformed:<the text as written>` and left raw: a guessed
63
+ * target would be a silent fallback (CLAUDE.md §2), and ignoring it
64
+ * would ship a dead `caelo:` href. Only link positions count (a whole
65
+ * value, an attribute value, a srcset entry); prose that mentions the
66
+ * grammar is left alone.
67
+ */
68
+ export const LINK_REF_MALFORMED = "link-ref-malformed";
69
+ // Pieces of the grammar. Every quantifier is bounded or runs over a
70
+ // character class that cannot overlap its neighbour, and every
71
+ // alternative starts with a literal, so scanning is linear (CodeQL
72
+ // js/polynomial-redos; the budget tests in link-refs.test.ts pin it).
73
+ const UUID_SRC = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
74
+ // Query: URL query characters, `&amp;`-escaped ampersands included; no
75
+ // quote, space, angle bracket, `#` or comma (a comma ends a srcset entry).
76
+ const QUERY_SRC = "[A-Za-z0-9_\\-.~%=&;+/:@!$()*]{1,512}";
77
+ // Fragment: what an HTML id / URL fragment realistically carries.
78
+ const ANCHOR_SRC = "[A-Za-z0-9_\\-.~%:]{1,256}";
79
+ const VARIANT_SRC = "[a-z][a-z0-9-]{0,63}";
80
+ // `caelo:` is a scheme only where a scheme can start: `xcaelo:page/…`
81
+ // is some other scheme's text, not a reference.
82
+ const REF_START_SRC = "(?<![A-Za-z0-9+.\\-])caelo:";
83
+ // An id (or media variant) must END where the grammar says: a following
84
+ // id or path character (`<id>abc`, `<id>/team`, `/webp-800/x`) makes the
85
+ // whole thing malformed instead of resolving the prefix and gluing the
86
+ // rest onto the URL.
87
+ const ID_END_SRC = "(?![A-Za-z0-9_/\\-])";
88
+ // Fallback alternative: tried only where both grammar alternatives fail
89
+ // at the same position, and only in LINK positions — the start of the
90
+ // string (a whole field value), of an attribute value (after a quote or
91
+ // `=`) or of a srcset entry (after a comma). Prose that talks about the
92
+ // grammar (`<code>caelo:page/&lt;id&gt;</code>` in a docs page) is text,
93
+ // not a broken link. Captures a bounded snippet for the marker.
94
+ const MALFORMED_SRC = "(?<=^|[\"'=,]|, )caelo:(?:page|media)/[^\\s\"'<>,]{0,96}";
95
+ const PAGE_BODY_SRC = `page/(${UUID_SRC})${ID_END_SRC}(?:\\?(${QUERY_SRC}))?(?:#(${ANCHOR_SRC}))?`;
96
+ const MEDIA_BODY_SRC = `media/(${UUID_SRC})(?:/(${VARIANT_SRC}))?${ID_END_SRC}`;
97
+ /**
98
+ * Global scanner for references embedded anywhere in a string. Groups:
99
+ * 1-3 page (id, query, anchor), 4-5 media (id, variant); a match with
100
+ * neither is malformed.
101
+ */
102
+ function linkRefScanner() {
103
+ // A fresh RegExp per call: global regexes carry `lastIndex` state.
104
+ // No alternative accepts a quote, so scanning an HTML attribute value
105
+ // stops at its boundary.
106
+ return new RegExp(`${REF_START_SRC}(?:${PAGE_BODY_SRC}|${MEDIA_BODY_SRC})|${MALFORMED_SRC}`, "g");
107
+ }
108
+ const PAGE_REF_EXACT = new RegExp(`^caelo:${PAGE_BODY_SRC}$`);
109
+ const MEDIA_REF_EXACT = new RegExp(`^caelo:${MEDIA_BODY_SRC}$`);
110
+ /** The reference a scanner match denotes, or null for a malformed one. */
111
+ function refFromMatch(m) {
112
+ if (m[1] !== undefined) {
113
+ return { kind: "page", pageId: m[1], query: m[2] ?? null, anchor: m[3] ?? null };
114
+ }
115
+ if (m[4] !== undefined)
116
+ return { kind: "media", mediaId: m[4], variant: m[5] ?? null };
117
+ return null;
118
+ }
119
+ /**
120
+ * Parse a string that IS exactly one reference (a whole field value).
121
+ * Returns null for anything else — a path, an external URL, or a string
122
+ * that merely contains a reference.
123
+ */
124
+ export function parseLinkRef(value) {
125
+ if (!value.startsWith(LINK_REF_SCHEME))
126
+ return null;
127
+ const page = PAGE_REF_EXACT.exec(value);
128
+ if (page) {
129
+ return {
130
+ kind: "page",
131
+ pageId: page[1],
132
+ query: page[2] ?? null,
133
+ anchor: page[3] ?? null,
134
+ };
135
+ }
136
+ const media = MEDIA_REF_EXACT.exec(value);
137
+ if (media)
138
+ return { kind: "media", mediaId: media[1], variant: media[2] ?? null };
139
+ return null;
140
+ }
141
+ /**
142
+ * Serialise a reference to its stored string form.
143
+ *
144
+ * @throws when the result would not parse back (an id that is not a
145
+ * lowercase UUID, a query / anchor / variant outside the grammar's
146
+ * character classes): storing it would write a link no render can
147
+ * resolve. Callers validate author input before building a ref.
148
+ */
149
+ export function formatLinkRef(ref) {
150
+ const out = ref.kind === "media"
151
+ ? `${LINK_REF_SCHEME}media/${ref.mediaId}${ref.variant ? `/${ref.variant}` : ""}`
152
+ : `${LINK_REF_SCHEME}page/${ref.pageId}` +
153
+ (ref.query ? `?${ref.query}` : "") +
154
+ (ref.anchor ? `#${ref.anchor}` : "");
155
+ if (parseLinkRef(out) === null) {
156
+ throw new Error(`formatLinkRef: ${JSON.stringify(out)} is not a valid internal reference ` +
157
+ "(ids are lowercase UUIDs; query, anchor and variant must use the grammar's characters, see link-refs.ts)");
158
+ }
159
+ return out;
160
+ }
161
+ /**
162
+ * The target-identity half of a reference, `page/<id>` / `media/<id>` —
163
+ * what failure markers and integrity reports name (query, anchor and
164
+ * variant do not change whether the target exists).
165
+ */
166
+ export function linkRefTargetKey(ref) {
167
+ return ref.kind === "page" ? `page/${ref.pageId}` : `media/${ref.mediaId}`;
168
+ }
169
+ /** Cheap pre-check: can `text` contain a reference at all? */
170
+ export function mayContainLinkRef(text) {
171
+ return text.includes(LINK_REF_SCHEME);
172
+ }
173
+ /**
174
+ * Every well-formed reference embedded in `text`, in order (duplicates
175
+ * kept). Malformed ones are not references; see
176
+ * {@link findMalformedLinkRefs}.
177
+ */
178
+ export function findLinkRefs(text) {
179
+ if (!mayContainLinkRef(text))
180
+ return [];
181
+ const out = [];
182
+ for (const m of text.matchAll(linkRefScanner())) {
183
+ const ref = refFromMatch(m);
184
+ if (ref)
185
+ out.push(ref);
186
+ }
187
+ return out;
188
+ }
189
+ /**
190
+ * Every piece of `text` that starts like a reference (`caelo:page/`,
191
+ * `caelo:media/`) but breaks the grammar, exactly as written (bounded
192
+ * snippet), in order.
193
+ */
194
+ export function findMalformedLinkRefs(text) {
195
+ if (!mayContainLinkRef(text))
196
+ return [];
197
+ const out = [];
198
+ for (const m of text.matchAll(linkRefScanner())) {
199
+ if (refFromMatch(m) === null)
200
+ out.push(m[0]);
201
+ }
202
+ return out;
203
+ }
204
+ /**
205
+ * Replace every well-formed reference embedded in `text` with
206
+ * `replacer`'s answer. `raw` is the reference exactly as it appeared, so
207
+ * a replacer can leave it in place (return `raw`) — the loud-raw
208
+ * convention for a target that cannot be resolved. Malformed references
209
+ * are left untouched.
210
+ */
211
+ export function replaceLinkRefs(text, replacer) {
212
+ if (!mayContainLinkRef(text))
213
+ return text;
214
+ return text.replace(linkRefScanner(), (...args) => {
215
+ const raw = args[0];
216
+ const ref = refFromMatch(args.slice(0, 6));
217
+ return ref ? replacer(ref, raw) : raw;
218
+ });
219
+ }
220
+ /**
221
+ * Walk a JSON value (content values, field defaults, set items) and map
222
+ * every string through {@link replaceLinkRefs}. Returns the SAME
223
+ * reference when nothing changed, so callers can cheaply detect no-ops.
224
+ */
225
+ export function mapLinkRefsDeep(value, replacer) {
226
+ if (typeof value === "string")
227
+ return replaceLinkRefs(value, replacer);
228
+ if (Array.isArray(value)) {
229
+ let changed = false;
230
+ const next = value.map((v) => {
231
+ const r = mapLinkRefsDeep(v, replacer);
232
+ if (r !== v)
233
+ changed = true;
234
+ return r;
235
+ });
236
+ return changed ? next : value;
237
+ }
238
+ if (value !== null && typeof value === "object") {
239
+ let changed = false;
240
+ const out = {};
241
+ for (const [k, v] of Object.entries(value)) {
242
+ const r = mapLinkRefsDeep(v, replacer);
243
+ if (r !== v)
244
+ changed = true;
245
+ out[k] = r;
246
+ }
247
+ return changed ? out : value;
248
+ }
249
+ return value;
250
+ }
251
+ /** Every reference anywhere inside a JSON value. */
252
+ export function collectLinkRefsDeep(value) {
253
+ const out = [];
254
+ mapLinkRefsDeep(value, (ref, raw) => {
255
+ out.push(ref);
256
+ return raw;
257
+ });
258
+ return out;
259
+ }
260
+ /**
261
+ * `missingSlots` marker for a reference whose target does not exist:
262
+ * `link-target-missing:page/<id>` / `link-target-missing:media/<id>`.
263
+ * The editor's missing-content surface and the static generator's
264
+ * render gate read these literal strings.
265
+ */
266
+ export const LINK_TARGET_MISSING = "link-target-missing";
267
+ /**
268
+ * `missingSlots` marker for a reference rendered by a caller that gave
269
+ * the engine no resolver — never legitimate output; a deploy refuses it.
270
+ */
271
+ export const LINK_RESOLVER_UNAVAILABLE = "link-resolver-unavailable";
272
+ /**
273
+ * True for every link failure marker: a missing target, a malformed
274
+ * reference and a render without a resolver. The static generator's
275
+ * render gate and the nested-module echo filter both key on this, so a
276
+ * new marker added here blocks a deploy everywhere at once.
277
+ */
278
+ export function isLinkFailureMarker(marker) {
279
+ return (marker.startsWith(`${LINK_TARGET_MISSING}:`) ||
280
+ marker.startsWith(`${LINK_REF_MALFORMED}:`) ||
281
+ marker.startsWith(`${LINK_RESOLVER_UNAVAILABLE}:`));
282
+ }
283
+ /** The link failure markers among `markers`, in order. */
284
+ export function linkMarkersOf(markers) {
285
+ return markers.filter(isLinkFailureMarker);
286
+ }
287
+ /**
288
+ * Resolve every reference embedded in rendered HTML. Unresolvable
289
+ * references stay in the output verbatim (loud-raw, CLAUDE.md §2) and
290
+ * push a marker onto `missing`; `unpublished` ones render their URL and
291
+ * push nothing — that is a per-environment policy, not a render failure.
292
+ *
293
+ * Text that starts like a reference but breaks the grammar stays raw
294
+ * and pushes {@link LINK_REF_MALFORMED}, with or without a resolver.
295
+ *
296
+ * @param resolver undefined = the caller has no resolver; every
297
+ * reference then fails with {@link LINK_RESOLVER_UNAVAILABLE}.
298
+ */
299
+ export function resolveLinkRefsInHtml(html, resolver, missing) {
300
+ for (const raw of findMalformedLinkRefs(html))
301
+ missing.push(`${LINK_REF_MALFORMED}:${raw}`);
302
+ return replaceLinkRefs(html, (ref, raw) => {
303
+ if (!resolver) {
304
+ missing.push(`${LINK_RESOLVER_UNAVAILABLE}:${linkRefTargetKey(ref)}`);
305
+ return raw;
306
+ }
307
+ const r = resolver.resolve(ref);
308
+ if (r.status === "missing") {
309
+ missing.push(`${LINK_TARGET_MISSING}:${linkRefTargetKey(ref)}`);
310
+ return raw;
311
+ }
312
+ return r.url;
313
+ });
314
+ }
315
+ /**
316
+ * The per-environment policy for a reference that did not resolve
317
+ * cleanly (maintainer decision on #724): a missing target blocks
318
+ * everywhere; an unpublished one is a warning at Stage and blocks
319
+ * Publish live / promote. The editor preview shows drafts, so nothing
320
+ * there is an issue beyond a missing target.
321
+ */
322
+ export function linkIssueSeverity(env, status) {
323
+ if (status === "missing")
324
+ return "block";
325
+ if (status === "unpublished") {
326
+ if (env === "production")
327
+ return "block";
328
+ if (env === "staging")
329
+ return "warning";
330
+ }
331
+ return null;
332
+ }
333
+ /**
334
+ * The URL path a page is served at under `style`: `/` for the root,
335
+ * `/a/b/` (directory) or `/a/b` (no-extension) otherwise. Same shape as
336
+ * the path half of `resolveCanonicalUrl`.
337
+ */
338
+ export function pageHrefForStyle(path, style) {
339
+ const trimmed = trimSlashes(path);
340
+ if (trimmed.length === 0)
341
+ return "/";
342
+ return style === "no-extension" ? `/${trimmed}` : `/${trimmed}/`;
343
+ }
344
+ /** Build the URL a resolved page reference renders as. */
345
+ function pageRefUrl(ref, path, style) {
346
+ return (pageHrefForStyle(path, style) +
347
+ (ref.query ? `?${ref.query}` : "") +
348
+ (ref.anchor ? `#${ref.anchor}` : ""));
349
+ }
350
+ /**
351
+ * Build the render-wide link index. Media references resolve to the
352
+ * editor-served media URL (`/_caelo/media/<slug>[/<variant>]`) — the
353
+ * same URL a literal embed carries — so the preview's responsive-image
354
+ * enrichment and the static generator's media pass (content-hashed
355
+ * `/_assets/…`, srcset ladders) apply to them unchanged (#162 parity).
356
+ */
357
+ export function createSiteLinkIndex(input) {
358
+ const pages = new Map(input.pages.map((p) => [p.id, p]));
359
+ const media = new Map(input.media.map((m) => [m.id, m]));
360
+ const resolve = (ref) => {
361
+ if (ref.kind === "media") {
362
+ const asset = media.get(ref.mediaId);
363
+ if (!asset)
364
+ return { status: "missing" };
365
+ return { status: "resolved", url: buildMediaUrl(asset.slug, ref.variant ?? "orig") };
366
+ }
367
+ const page = pages.get(ref.pageId);
368
+ if (!page)
369
+ return { status: "missing" };
370
+ const url = pageRefUrl(ref, page.path, input.pageUrlStyle);
371
+ if (input.env !== "preview" && page.status !== "published") {
372
+ return { status: "unpublished", url };
373
+ }
374
+ return { status: "resolved", url };
375
+ };
376
+ return {
377
+ env: input.env,
378
+ pathOf: (pageId) => pages.get(pageId)?.path ?? null,
379
+ forPage: () => {
380
+ const observed = [];
381
+ return {
382
+ observed,
383
+ resolve(ref) {
384
+ const resolution = resolve(ref);
385
+ observed.push({ ref, resolution });
386
+ return resolution;
387
+ },
388
+ };
389
+ },
390
+ };
391
+ }
392
+ /**
393
+ * Turn stored references back into what an author writes: `/about`,
394
+ * `/about#team`, `/_caelo/media/hero`. Applied to every read that shows
395
+ * content to the AI or the operator, so neither ever has to learn the
396
+ * grammar (CLAUDE.md §1A); writing the humanised form back
397
+ * canonicalises to the same reference.
398
+ *
399
+ * A reference whose target is gone stays raw — it is broken, and a
400
+ * plausible-looking path would hide that.
401
+ */
402
+ export function humaniseLinkRefs(value, lookup) {
403
+ return mapLinkRefsDeep(value, (ref, raw) => {
404
+ if (ref.kind === "media") {
405
+ const slug = lookup.mediaSlug(ref.mediaId);
406
+ if (slug === null)
407
+ return raw;
408
+ return buildMediaUrl(slug, ref.variant ?? "orig");
409
+ }
410
+ const path = lookup.pagePath(ref.pageId);
411
+ if (path === null)
412
+ return raw;
413
+ return `${path}${ref.query ? `?${ref.query}` : ""}${ref.anchor ? `#${ref.anchor}` : ""}`;
414
+ });
415
+ }
416
+ //# sourceMappingURL=link-refs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-refs.js","sourceRoot":"","sources":["../src/link-refs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /** One element of parsed markup. Text is dropped; it never matters here. */
2
+ export interface MarkupNode {
3
+ readonly tag: string;
4
+ readonly classes: readonly string[];
5
+ /** The `data-caelo-module-id` the composer stamps on every module root. */
6
+ readonly moduleId: string | null;
7
+ readonly children: MarkupNode[];
8
+ readonly parent: MarkupNode | null;
9
+ }
10
+ /** Most elements one parse keeps; the rest of the document is ignored. */
11
+ export declare const MAX_NODES = 50000;
12
+ /** A pattern has at least this many distinct nodes… */
13
+ export declare const MIN_PATTERN_NODES = 4;
14
+ /** …spread over at least this many levels (root, child, grandchild). */
15
+ export declare const MIN_PATTERN_DEPTH = 3;
16
+ /** Jaccard similarity at or above which two patterns are "near" matches. */
17
+ export declare const NEAR_MATCH_THRESHOLD = 0.8;
18
+ /** Parse markup into a forest of element nodes. */
19
+ export declare function parseMarkup(html: string): MarkupNode[];
20
+ /**
21
+ * Remove Mustache tags (`{{x}}`, `{{{x}}}`, `{{#s}}`, `{{/s}}`, `{{>p}}`)
22
+ * from a module template so it parses as the markup it renders: a section
23
+ * body stays once, a placeholder becomes empty.
24
+ */
25
+ export declare function stripMustache(template: string): string;
26
+ /** The node's key without its children: `div.card.card--wide`. */
27
+ export declare function nodeKey(n: Pick<MarkupNode, "tag" | "classes">): string;
28
+ /** Structural facts about one subtree. */
29
+ export interface SubtreeShape {
30
+ /** Repetition- and text-independent structural signature. */
31
+ readonly signature: string;
32
+ /** Distinct nodes after repetition collapse. */
33
+ readonly nodeCount: number;
34
+ /** Levels, the subtree root counting as 1. */
35
+ readonly depth: number;
36
+ /** Whether any node in it carries a class. */
37
+ readonly hasClass: boolean;
38
+ /** Parent→child edges, for near-match similarity. */
39
+ readonly edges: ReadonlySet<string>;
40
+ }
41
+ /**
42
+ * Compute the shapes of a subtree and all its descendants in one pass.
43
+ * Nested module roots are opaque: their own module owns them.
44
+ */
45
+ export declare function shapeTree(root: MarkupNode, out?: Map<MarkupNode, SubtreeShape>): Map<MarkupNode, SubtreeShape>;
46
+ /** Whether a subtree is big and specific enough to count as a pattern. */
47
+ export declare function isPattern(shape: SubtreeShape): boolean;
48
+ /** Jaccard similarity of two shapes' edge sets, 0…1. */
49
+ export declare function shapeSimilarity(a: SubtreeShape, b: SubtreeShape): number;
50
+ /** A qualifying pattern found in a piece of markup. */
51
+ export interface FoundPattern {
52
+ readonly node: MarkupNode;
53
+ readonly shape: SubtreeShape;
54
+ }
55
+ /**
56
+ * The outermost qualifying patterns in a forest: a pattern inside a bigger
57
+ * pattern is part of it and not reported on its own.
58
+ */
59
+ export declare function outermostPatterns(roots: readonly MarkupNode[]): FoundPattern[];
60
+ /** The shape of a whole module template (its first element), or null. */
61
+ export declare function templateShape(templateHtml: string): SubtreeShape | null;
62
+ /**
63
+ * The parts of a rendered module that did NOT come from its template: the
64
+ * markup field values injected (richtext, text). The rendered tree is
65
+ * walked in step with the template tree; a rendered child that no template
66
+ * child can account for (same tag, template's classes all present) starts
67
+ * a free region. List sections still match, because every iteration of a
68
+ * section renders the same template child.
69
+ *
70
+ * @returns the free region roots, or null when the rendered root does not
71
+ * match the template root at all (a module the composer renders from
72
+ * other data, such as a navigation menu) — nothing can be attributed then.
73
+ */
74
+ export declare function freeRegions(rendered: MarkupNode, templateRoots: readonly MarkupNode[]): MarkupNode[] | null;
75
+ /** Every module root (`data-caelo-module-id`) in a forest, nested ones included. */
76
+ export declare function moduleRoots(roots: readonly MarkupNode[]): MarkupNode[];
77
+ /** A short operator-readable name for a pattern: its root key, e.g. `div.chain`. */
78
+ export declare function patternLabel(p: Pick<MarkupNode, "tag" | "classes">): string;
79
+ //# sourceMappingURL=markup-structure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markup-structure.d.ts","sourceRoot":"","sources":["../src/markup-structure.ts"],"names":[],"mappings":"AAoCA,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CACpC;AAED,0EAA0E;AAC1E,eAAO,MAAM,SAAS,QAAS,CAAC;AAChC,uDAAuD;AACvD,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAKxC,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAsCtD;AAeD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AA+BD,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG,SAAS,CAAC,GAAG,MAAM,CAEtE;AAED,0CAA0C;AAC1C,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACrC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,gCAAsC,iCAiCpF;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAEtD;AAED,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM,CAMxE;AAED,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,YAAY,EAAE,CAY9E;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAGvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,UAAU,EACpB,aAAa,EAAE,SAAS,UAAU,EAAE,GACnC,UAAU,EAAE,GAAG,IAAI,CAcrB;AAQD,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,EAAE,CAQtE;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG,SAAS,CAAC,GAAG,MAAM,CAE3E"}