@jini-ai/cms 0.5.2 → 0.5.4

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 (261) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +14 -8
  3. package/dist/comments/errors.d.ts +23 -0
  4. package/dist/comments/errors.js +25 -0
  5. package/dist/comments/express/index.d.ts +2 -0
  6. package/dist/comments/express/index.js +2 -0
  7. package/dist/comments/express/submit-handler.d.ts +30 -0
  8. package/dist/comments/express/submit-handler.js +85 -0
  9. package/dist/comments/hooks.d.ts +39 -0
  10. package/dist/comments/hooks.js +41 -0
  11. package/dist/comments/index.d.ts +11 -0
  12. package/dist/comments/index.js +11 -0
  13. package/dist/comments/ingress.d.ts +57 -0
  14. package/dist/comments/ingress.js +173 -0
  15. package/dist/comments/module.d.ts +94 -0
  16. package/dist/comments/module.js +73 -0
  17. package/dist/comments/ports.d.ts +184 -0
  18. package/dist/comments/ports.js +2 -0
  19. package/dist/comments/repo.memory.d.ts +70 -0
  20. package/dist/comments/repo.memory.js +120 -0
  21. package/dist/comments/sanitize.d.ts +15 -0
  22. package/dist/comments/sanitize.js +31 -0
  23. package/dist/comments/spam.heuristic.d.ts +8 -0
  24. package/dist/comments/spam.heuristic.js +54 -0
  25. package/dist/comments/sql/index.d.ts +3 -0
  26. package/dist/comments/sql/index.js +3 -0
  27. package/dist/comments/sql/repo.d.ts +77 -0
  28. package/dist/comments/sql/repo.js +190 -0
  29. package/dist/comments/sql/repo.rows.d.ts +49 -0
  30. package/dist/comments/sql/repo.rows.js +71 -0
  31. package/dist/comments/types.d.ts +187 -0
  32. package/dist/comments/types.js +39 -0
  33. package/dist/comments/write-service.d.ts +121 -0
  34. package/dist/comments/write-service.js +144 -0
  35. package/dist/core/commands/command.d.ts +2 -9
  36. package/dist/core/commands/command.js +2 -9
  37. package/dist/core/index.d.ts +2 -1
  38. package/dist/core/index.js +1 -0
  39. package/dist/core/keyset-cursor.d.ts +25 -0
  40. package/dist/core/keyset-cursor.js +20 -0
  41. package/dist/core/ports.d.ts +30 -0
  42. package/dist/core/ports.js +0 -1
  43. package/dist/core/tools/args.d.ts +1 -46
  44. package/dist/core/tools/args.js +1 -44
  45. package/dist/forms/delete-submission.d.ts +38 -0
  46. package/dist/forms/delete-submission.js +25 -0
  47. package/dist/forms/duplicate-slug.d.ts +81 -0
  48. package/dist/forms/duplicate-slug.js +115 -0
  49. package/dist/forms/errors.d.ts +53 -0
  50. package/dist/forms/errors.js +25 -0
  51. package/dist/forms/express/handler.d.ts +59 -0
  52. package/dist/forms/express/handler.js +217 -0
  53. package/dist/forms/express/index.d.ts +2 -0
  54. package/dist/forms/express/index.js +2 -0
  55. package/dist/forms/fields-json.d.ts +14 -0
  56. package/dist/forms/fields-json.js +11 -0
  57. package/dist/forms/forms.d.ts +59 -0
  58. package/dist/forms/forms.js +213 -0
  59. package/dist/forms/html/authoring-adapter.d.ts +34 -0
  60. package/dist/forms/html/authoring-adapter.js +48 -0
  61. package/dist/forms/html/html-authoring.d.ts +12 -0
  62. package/dist/forms/html/html-authoring.js +133 -0
  63. package/dist/forms/html/html-submission-adapter.d.ts +10 -0
  64. package/dist/forms/html/html-submission-adapter.js +15 -0
  65. package/dist/forms/html/index.d.ts +4 -0
  66. package/dist/forms/html/index.js +4 -0
  67. package/dist/forms/html-render.d.ts +33 -0
  68. package/dist/forms/html-render.js +31 -0
  69. package/dist/forms/index.d.ts +17 -0
  70. package/dist/forms/index.js +17 -0
  71. package/dist/forms/notify-subscriber.d.ts +32 -0
  72. package/dist/forms/notify-subscriber.js +60 -0
  73. package/dist/forms/ports.d.ts +168 -0
  74. package/dist/forms/ports.js +2 -0
  75. package/dist/forms/rate-limit-key.d.ts +8 -0
  76. package/dist/forms/rate-limit-key.js +7 -0
  77. package/dist/forms/repo.memory.d.ts +120 -0
  78. package/dist/forms/repo.memory.js +188 -0
  79. package/dist/forms/sql/index.d.ts +5 -0
  80. package/dist/forms/sql/index.js +5 -0
  81. package/dist/forms/sql/repo.d.ts +94 -0
  82. package/dist/forms/sql/repo.js +175 -0
  83. package/dist/forms/sql/repo.rows.d.ts +33 -0
  84. package/dist/forms/sql/repo.rows.js +64 -0
  85. package/dist/forms/sql/submission-ip-retention-repo.d.ts +9 -0
  86. package/dist/forms/sql/submission-ip-retention-repo.js +40 -0
  87. package/dist/forms/sql/tables.d.ts +32 -0
  88. package/dist/forms/sql/tables.js +2 -0
  89. package/dist/forms/submission-attempts.d.ts +70 -0
  90. package/dist/forms/submission-attempts.js +70 -0
  91. package/dist/forms/submission-email-validation.d.ts +8 -0
  92. package/dist/forms/submission-email-validation.js +34 -0
  93. package/dist/forms/submission-ip-retention.d.ts +30 -0
  94. package/dist/forms/submission-ip-retention.js +27 -0
  95. package/dist/forms/submit-service.d.ts +78 -0
  96. package/dist/forms/submit-service.js +109 -0
  97. package/dist/forms/types.d.ts +49 -0
  98. package/dist/forms/types.js +2 -0
  99. package/dist/forms/write-service.d.ts +83 -0
  100. package/dist/forms/write-service.js +271 -0
  101. package/dist/media/index.d.ts +0 -3
  102. package/dist/media/index.js +1 -4
  103. package/dist/media/node.d.ts +5 -0
  104. package/dist/media/node.js +6 -0
  105. package/dist/redirects/capture.d.ts +80 -0
  106. package/dist/redirects/capture.js +66 -0
  107. package/dist/redirects/express/index.d.ts +27 -0
  108. package/dist/redirects/express/index.js +40 -0
  109. package/dist/redirects/hit-sink.d.ts +56 -0
  110. package/dist/redirects/hit-sink.js +44 -0
  111. package/dist/redirects/host-config/index.d.ts +40 -0
  112. package/dist/redirects/host-config/index.js +145 -0
  113. package/dist/redirects/index.d.ts +14 -0
  114. package/dist/redirects/index.js +14 -0
  115. package/dist/redirects/matcher.d.ts +58 -0
  116. package/dist/redirects/matcher.js +144 -0
  117. package/dist/redirects/order.d.ts +30 -0
  118. package/dist/redirects/order.js +26 -0
  119. package/dist/redirects/ports.d.ts +242 -0
  120. package/dist/redirects/ports.internal.d.ts +57 -0
  121. package/dist/redirects/ports.internal.js +14 -0
  122. package/dist/redirects/ports.js +2 -0
  123. package/dist/redirects/redirects.d.ts +226 -0
  124. package/dist/redirects/redirects.js +590 -0
  125. package/dist/redirects/referrer-alias.d.ts +31 -0
  126. package/dist/redirects/referrer-alias.js +33 -0
  127. package/dist/redirects/repo.memory.d.ts +51 -0
  128. package/dist/redirects/repo.memory.js +98 -0
  129. package/dist/redirects/reserved-destination.d.ts +58 -0
  130. package/dist/redirects/reserved-destination.js +59 -0
  131. package/dist/redirects/resolver.d.ts +100 -0
  132. package/dist/redirects/resolver.js +87 -0
  133. package/dist/redirects/spa/index.d.ts +30 -0
  134. package/dist/redirects/spa/index.js +27 -0
  135. package/dist/redirects/sql/index.d.ts +4 -0
  136. package/dist/redirects/sql/index.js +4 -0
  137. package/dist/redirects/sql/repo.d.ts +82 -0
  138. package/dist/redirects/sql/repo.js +143 -0
  139. package/dist/redirects/sql/repo.rows.d.ts +49 -0
  140. package/dist/redirects/sql/repo.rows.js +79 -0
  141. package/dist/redirects/sql/row-types.d.ts +34 -0
  142. package/dist/redirects/sql/row-types.js +2 -0
  143. package/dist/redirects/target-gate.d.ts +30 -0
  144. package/dist/redirects/target-gate.js +85 -0
  145. package/dist/redirects/types.d.ts +213 -0
  146. package/dist/redirects/types.js +42 -0
  147. package/dist/seo/absolute-url.d.ts +58 -0
  148. package/dist/seo/absolute-url.js +117 -0
  149. package/dist/seo/dom/index.d.ts +14 -0
  150. package/dist/seo/dom/index.js +52 -0
  151. package/dist/seo/errors.d.ts +35 -0
  152. package/dist/seo/errors.js +35 -0
  153. package/dist/seo/feed.d.ts +49 -0
  154. package/dist/seo/feed.js +61 -0
  155. package/dist/seo/index.d.ts +13 -0
  156. package/dist/seo/index.js +13 -0
  157. package/dist/seo/media.d.ts +57 -0
  158. package/dist/seo/media.js +134 -0
  159. package/dist/seo/page-head.d.ts +18 -0
  160. package/dist/seo/page-head.js +114 -0
  161. package/dist/seo/ports.d.ts +221 -0
  162. package/dist/seo/ports.js +27 -0
  163. package/dist/seo/seo.d.ts +73 -0
  164. package/dist/seo/seo.js +230 -0
  165. package/dist/seo/sitemap.d.ts +139 -0
  166. package/dist/seo/sitemap.js +217 -0
  167. package/dist/seo/types.d.ts +278 -0
  168. package/dist/seo/types.js +2 -0
  169. package/dist/seo/write-service.d.ts +79 -0
  170. package/dist/seo/write-service.js +273 -0
  171. package/dist/seo/xml-escape.d.ts +16 -0
  172. package/dist/seo/xml-escape.js +19 -0
  173. package/dist/settings/express/cms-adapter.d.ts +20 -0
  174. package/dist/settings/express/cms-adapter.js +124 -0
  175. package/dist/settings/express/contracts.d.ts +135 -0
  176. package/dist/settings/express/contracts.js +2 -0
  177. package/dist/settings/express/http.d.ts +10 -0
  178. package/dist/settings/express/http.js +211 -0
  179. package/dist/settings/express/index.d.ts +5 -0
  180. package/dist/settings/express/index.js +5 -0
  181. package/dist/settings/express/shared.d.ts +68 -0
  182. package/dist/settings/express/shared.js +54 -0
  183. package/dist/settings/express/stream.d.ts +24 -0
  184. package/dist/settings/express/stream.js +147 -0
  185. package/dist/settings/index.d.ts +3 -38
  186. package/dist/settings/index.js +3 -35
  187. package/dist/taxonomy/write-service.d.ts +2 -30
  188. package/dist/widgets/authorize-helper.d.ts +42 -0
  189. package/dist/widgets/authorize-helper.js +16 -0
  190. package/dist/widgets/concurrency.d.ts +12 -0
  191. package/dist/widgets/concurrency.js +44 -0
  192. package/dist/widgets/config-validation.d.ts +34 -0
  193. package/dist/widgets/config-validation.js +125 -0
  194. package/dist/widgets/embed-service.d.ts +133 -0
  195. package/dist/widgets/embed-service.js +460 -0
  196. package/dist/widgets/embed-validation.d.ts +23 -0
  197. package/dist/widgets/embed-validation.js +38 -0
  198. package/dist/widgets/entry-payload.d.ts +103 -0
  199. package/dist/widgets/entry-payload.js +160 -0
  200. package/dist/widgets/errors.d.ts +136 -0
  201. package/dist/widgets/errors.js +136 -0
  202. package/dist/widgets/host-ports.d.ts +149 -0
  203. package/dist/widgets/host-ports.js +2 -0
  204. package/dist/widgets/html/html-embeds.d.ts +247 -0
  205. package/dist/widgets/html/html-embeds.js +408 -0
  206. package/dist/widgets/html/index.d.ts +5 -0
  207. package/dist/widgets/html/index.js +4 -0
  208. package/dist/widgets/html/legacy-widget-markers.d.ts +8 -0
  209. package/dist/widgets/html/legacy-widget-markers.js +45 -0
  210. package/dist/widgets/html/resolve-html-page-embeds.d.ts +42 -0
  211. package/dist/widgets/html/resolve-html-page-embeds.js +57 -0
  212. package/dist/widgets/index.d.ts +20 -0
  213. package/dist/widgets/index.js +19 -0
  214. package/dist/widgets/markers/html-attributes.d.ts +98 -0
  215. package/dist/widgets/markers/html-attributes.js +216 -0
  216. package/dist/widgets/markers/index.d.ts +3 -0
  217. package/dist/widgets/markers/index.js +3 -0
  218. package/dist/widgets/markers/marker.d.ts +496 -0
  219. package/dist/widgets/markers/marker.js +780 -0
  220. package/dist/widgets/page-embed-types.d.ts +42 -0
  221. package/dist/widgets/page-embed-types.js +42 -0
  222. package/dist/widgets/ports.d.ts +142 -0
  223. package/dist/widgets/ports.js +2 -0
  224. package/dist/widgets/read-service.d.ts +81 -0
  225. package/dist/widgets/read-service.js +79 -0
  226. package/dist/widgets/region-area-service.d.ts +110 -0
  227. package/dist/widgets/region-area-service.js +180 -0
  228. package/dist/widgets/registry.d.ts +217 -0
  229. package/dist/widgets/registry.js +172 -0
  230. package/dist/widgets/repo.memory.d.ts +41 -0
  231. package/dist/widgets/repo.memory.js +48 -0
  232. package/dist/widgets/resolver-service.d.ts +209 -0
  233. package/dist/widgets/resolver-service.js +909 -0
  234. package/dist/widgets/resolvers/contact-form.d.ts +26 -0
  235. package/dist/widgets/resolvers/contact-form.js +54 -0
  236. package/dist/widgets/resolvers/create-core-resolvers.d.ts +34 -0
  237. package/dist/widgets/resolvers/create-core-resolvers.js +11 -0
  238. package/dist/widgets/resolvers/index.d.ts +65 -0
  239. package/dist/widgets/resolvers/index.js +155 -0
  240. package/dist/widgets/resolvers/menu.d.ts +42 -0
  241. package/dist/widgets/resolvers/menu.js +43 -0
  242. package/dist/widgets/resolvers/ports.d.ts +82 -0
  243. package/dist/widgets/resolvers/ports.js +2 -0
  244. package/dist/widgets/resolvers/recent-entries.d.ts +71 -0
  245. package/dist/widgets/resolvers/recent-entries.js +165 -0
  246. package/dist/widgets/resolvers/taxonomy.d.ts +12 -0
  247. package/dist/widgets/resolvers/taxonomy.js +34 -0
  248. package/dist/widgets/sql/index.d.ts +3 -0
  249. package/dist/widgets/sql/index.js +3 -0
  250. package/dist/widgets/sql/repo.d.ts +41 -0
  251. package/dist/widgets/sql/repo.js +82 -0
  252. package/dist/widgets/sql/tables.d.ts +13 -0
  253. package/dist/widgets/sql/tables.js +2 -0
  254. package/dist/widgets/types.d.ts +213 -0
  255. package/dist/widgets/types.js +17 -0
  256. package/dist/widgets/where-used.d.ts +41 -0
  257. package/dist/widgets/where-used.js +11 -0
  258. package/dist/widgets/write-service.d.ts +204 -0
  259. package/dist/widgets/write-service.js +332 -0
  260. package/dist/workspace/delete.d.ts +1 -1
  261. package/package.json +220 -12
@@ -0,0 +1,496 @@
1
+ /**
2
+ * @file The ONE parser for embed markers (`data-embed-config`), shared by every consumer.
3
+ *
4
+ * Purpose:
5
+ * Before the 2026-08-10 marker-spine unification there were four independent scanners, each with its
6
+ * own regex, matching two different attribute vocabularies that had drifted apart:
7
+ *
8
+ * - `widgets/html-embeds.ts` — `data-embed-type`/`data-embed-id` in a Page's `body_html`
9
+ * - `features/theme/static-render.ts` — the same pair for menu/post markers, PLUS
10
+ * `data-tovu-slot`/`data-nav-current`/`data-slot-variant` for theme partials
11
+ * - `core/entry-refs/extractor.ts` — the same pair again, for reference extraction
12
+ *
13
+ * Four regexes for one concept is four places a fix has to land and four ways they can disagree
14
+ * (they already did: the widgets pipeline only matched a self-closing `<div></div>`, while the theme
15
+ * path deliberately matched a marker WITH authored fallback content inside it). This module is the
16
+ * single definition. Consumers ask it what a piece of HTML references; none of them owns a pattern.
17
+ *
18
+ * The vocabulary is one attribute carrying JSON:
19
+ *
20
+ * <div data-embed-config='{"type":"partial","id":"nav","current":"index"}'></div>
21
+ * <nav data-embed-config='{"type":"menu","id":"docs-nav","variant":"tree"}'>fallback</nav>
22
+ *
23
+ * Forms may request plain output with `{"type":"form","id":"contact","mode":"html"}`;
24
+ * `mode` is resolver-owned config, so the parser carries it through unchanged.
25
+ *
26
+ * `type` and `id` are ordinary keys, not separate attributes, so a new key never requires a new
27
+ * attribute name. The attribute is single-quoted precisely so the JSON's own double quotes need no
28
+ * escaping.
29
+ *
30
+ * Architectural role:
31
+ * PURE. No I/O, no DOM, no resolution — it reports what a marker *says*, never what it resolves to.
32
+ * Lives in `core/` because all three consumers above sit in different features and `core/entry-refs`
33
+ * (the integrity path) is already here.
34
+ *
35
+ * ## Why regex and not a parser
36
+ *
37
+ * Same reason the code it replaces used one: this runs server-side against theme-authored markup and
38
+ * Page bodies with no DOM available, and every consumer needs the marker's *source offsets* to
39
+ * substitute in place. A real HTML parse would also have to round-trip the document back to a string
40
+ * byte-identically, which is a much harder guarantee than locating one attribute.
41
+ */
42
+ /** A `data-embed-config` marker located in a piece of HTML. */
43
+ export interface EmbedMarker {
44
+ /** `config.type` — which resolver handles this. Always present; a marker without it is invalid. */
45
+ readonly type: string;
46
+ /**
47
+ * `config.id` — which instance. `undefined` for a type that needs no target (none today, but the
48
+ * shape allows one rather than forcing a meaningless id).
49
+ */
50
+ readonly id: string | undefined;
51
+ /** The whole parsed object, `type`/`id` included, so a resolver reads its own keys off one place. */
52
+ readonly config: Readonly<Record<string, unknown>>;
53
+ /** The marker element's tag name, for consumers that rebuild the open tag. */
54
+ readonly tag: string;
55
+ /** The marker's attribute string, verbatim, so other authored attributes survive a substitution. */
56
+ readonly attrs: string;
57
+ /** The full matched element including its inner content and closing tag. */
58
+ readonly whole: string;
59
+ /**
60
+ * The marker's own original inner content (between its open and close tag), exactly as authored.
61
+ * The mirror image of {@link withInnerContent}'s parameter: that function takes NEW inner content
62
+ * and keeps the marker's existing tag/attrs; this field is for a caller going the other way —
63
+ * keeping (or transforming) the marker's existing inner content while changing its attrs/config,
64
+ * e.g. {@link withAddedId} filling in a missing `id` without disturbing whatever fallback markup
65
+ * the author put inside the marker.
66
+ */
67
+ readonly inner: string;
68
+ /** Index of `whole` within the scanned html — substitution without re-searching. */
69
+ readonly index: number;
70
+ /** 1-based position among all markers found, for stable locators (`entry_refs` `fieldPath`). */
71
+ readonly occurrence: number;
72
+ }
73
+ /** Why a marker was rejected. Consumers decide whether to warn, skip, or refuse a write. */
74
+ export type EmbedMarkerProblem = {
75
+ readonly kind: "invalid-json";
76
+ readonly raw: string;
77
+ readonly message: string;
78
+ } | {
79
+ readonly kind: "not-an-object";
80
+ readonly raw: string;
81
+ } | {
82
+ readonly kind: "missing-type";
83
+ readonly raw: string;
84
+ };
85
+ /** One rejected marker, with enough context to name it in an error a human can act on. */
86
+ export interface EmbedMarkerRejection {
87
+ readonly problem: EmbedMarkerProblem;
88
+ readonly occurrence: number;
89
+ readonly index: number;
90
+ }
91
+ export interface ScanEmbedMarkersResult {
92
+ readonly markers: readonly EmbedMarker[];
93
+ /**
94
+ * Markers that carry the attribute but could not be understood. **Never silently empty-on-error**:
95
+ * `entry-refs/extractor.ts` builds the where-used index that safe-delete trusts, and a marker that
96
+ * fails to parse would otherwise drop a reference from that index without a trace — letting a
97
+ * delete proceed against something still in use. Extraction and the write chokepoint must treat a
98
+ * non-empty `rejected` as loud; render-time consumers may degrade quietly.
99
+ */
100
+ readonly rejected: readonly EmbedMarkerRejection[];
101
+ }
102
+ /**
103
+ * Produce a same-length copy of `html` with every HTML comment, and the raw-text content of every
104
+ * `<script>`/`<style>` element, replaced by space filler. {@link scanEmbedMarkers} runs
105
+ * {@link MARKER_PATTERN} against this copy — never the original — so a well-formed
106
+ * `data-embed-config` marker written inside an authoring note (`<!-- ... -->`) or inside a
107
+ * `<style>` block's CSS comment can never be matched: neither is ever parsed as an element by a
108
+ * browser, so the scanner must not treat either as one either. A marker outside both is untouched
109
+ * here and matches exactly as before.
110
+ *
111
+ * Same length is load-bearing, not cosmetic: every offset {@link scanEmbedMarkers} reports (and
112
+ * {@link substituteMarkers}'s index-based splice back into the ORIGINAL html) depends on a masked
113
+ * span occupying exactly the same positions as what it replaces. Deleting the comment/raw-text
114
+ * content instead of blanking it would shift every later offset out from under those callers.
115
+ *
116
+ * EXPORTED (2026-09-09) for `features/pages/regions.ts`, which scans the SAME page bodies for
117
+ * `data-agent-element` region elements and needs the identical "text inside a comment or inside a
118
+ * <style> block is not markup" rule with the identical offset-preserving guarantee. A second copy
119
+ * of this masking is exactly the four-scanners-for-one-concept drift this module's own header
120
+ * exists to close: region scanning is a different question about the same bytes, not a second
121
+ * vocabulary.
122
+ */
123
+ export declare function maskNonRenderableRegions({ html }: {
124
+ html: string;
125
+ }, _optional?: Record<string, never>): string;
126
+ /**
127
+ * Validate one marker's raw attribute text. Split out from {@link scanEmbedMarkers} so the scan stays
128
+ * a plain loop: all three rejection branches live here, and the caller only chooses which list to
129
+ * push onto.
130
+ *
131
+ * EXPORTED (2026-09-23, Bug A / interactive-bugs plan Slice A1) so a consumer that only has the raw
132
+ * `data-embed-config` string in hand — the admin's canvas placeholder describer, which reads an
133
+ * already-authored DOM attribute rather than re-running {@link scanEmbedMarkers} — can apply the exact
134
+ * same parse-and-reject rule `scanEmbedMarkers` itself uses, instead of growing its own copy of it.
135
+ */
136
+ export declare function parseEmbedMarkerConfig({ raw }: {
137
+ raw: string;
138
+ }, _optional?: Record<string, never>): {
139
+ config: Record<string, unknown>;
140
+ } | {
141
+ problem: EmbedMarkerProblem;
142
+ };
143
+ /**
144
+ * Locate and parse every embed marker in `html`. Pure; allocates one result per marker.
145
+ *
146
+ * Scans {@link maskNonRenderableRegions}'s masked copy so a marker sitting inside an HTML comment or
147
+ * a `<script>`/`<style>` element's raw text is never matched, and so tags inside either are invisible
148
+ * to {@link findBalancedClose}'s depth count too. Every field on the resulting {@link EmbedMarker}
149
+ * (`whole`, `tag`, `attrs`, `inner`, the parsed config) is read back out of the ORIGINAL `html` at
150
+ * that match's own offsets — masking only decides which spans are eligible to be a marker or to count
151
+ * toward a close tag's depth, it must never change what an eligible marker's own fields report.
152
+ *
153
+ * Uses a private copy of {@link MARKER_PATTERN} (same source/flags) for the `exec` loop below, rather
154
+ * than mutating the shared module-level regex's `lastIndex` directly — this function is PURE, and a
155
+ * shared mutable scan cursor would corrupt a reentrant or recursive call.
156
+ */
157
+ export declare function scanEmbedMarkers({ html }: {
158
+ html: string;
159
+ }, _optional?: Record<string, never>): ScanEmbedMarkersResult;
160
+ /**
161
+ * Rewrites every double-quoted, entity-encoded `data-embed-config="{&quot;type&quot;:…}"` (what a DOM
162
+ * serializer such as the admin's Interactive canvas emits) back to the authored, readable form
163
+ * `data-embed-config='{"type":…}'`, so the stored page source reads the way it was written. Every
164
+ * Page HTML write runs through this (`PagesHtmlDocumentStore`), and the admin applies it to what the
165
+ * canvas hands back.
166
+ *
167
+ * Exact at the config level: a rewritten marker scans to the same `config`, `attrs` and `inner` it did
168
+ * before. Only the quoted value changes; the tag, other attributes and content are untouched. Left
169
+ * as written: a single-quoted marker, a marker inside a comment or `<script>`/`<style>` (never matched,
170
+ * see {@link maskNonRenderableRegions}), and a value that is not valid JSON, so a broken marker still
171
+ * reaches {@link scanEmbedMarkers}'s rejection with its original text.
172
+ *
173
+ * @complexity O(n) over `html`.
174
+ */
175
+ export declare function normalizeEmbedMarkerQuoting({ html }: {
176
+ html: string;
177
+ }, _optional?: Record<string, never>): string;
178
+ /** Every marker of one type, in document order. The common "which menus does this theme name" shape. */
179
+ export declare function markersOfType({ html, type }: {
180
+ html: string;
181
+ type: string;
182
+ }, _optional?: Record<string, never>): readonly EmbedMarker[];
183
+ /**
184
+ * The two marker `type` values this module parses but never itself resolves — owned end-to-end by
185
+ * `features/theme/static-render.ts` (`injectMenuEmbeds` for {@link MENU_MARKER_TYPE}, `resolveSlots`
186
+ * for {@link PARTIAL_MARKER_TYPE}), which run AFTER `widgets/resolver-service.ts`'s page-embed stage
187
+ * on every static-tier page render.
188
+ *
189
+ * Hoisted here (2026-08-17) so that ownership fact has exactly one spelling. Before this, it existed
190
+ * only as inline string-literal comparisons inside `static-render.ts` itself
191
+ * (`marker.type !== "menu"`, `marker.type !== "partial"`), and `resolver-service.ts`'s own
192
+ * `THEME_OWNED_MARKER_TYPES` carried an independently-typed copy of the same two strings as a
193
+ * documented stopgap pending this exact hoist (`ADS-memory/reports/
194
+ * 2026-08-16-embed-placeholder-gap.md`, "Reuse decision") — two spellings of one fact is exactly the
195
+ * kind of drift risk this module exists to close for marker *parsing*; these two constants close it
196
+ * for marker *type naming* the same way.
197
+ *
198
+ * Deliberately NOT the same question `isPageEmbedType()` (`widgets/resolver-service.ts`) answers —
199
+ * that function tests registry membership (`Object.hasOwn(HTML_EMBED_RESOLVERS, type)`), which is
200
+ * `false` for these two AND for a genuine unregistered/typo type alike. These constants name the
201
+ * "owned elsewhere, not a typo" fact directly instead; see `isPageEmbedType`'s own doc for why
202
+ * conflating the two would silence a real warning.
203
+ */
204
+ export declare const MENU_MARKER_TYPE = "menu";
205
+ export declare const PARTIAL_MARKER_TYPE = "partial";
206
+ /**
207
+ * The post-previews marker type (2026-09-03) — a theme/page marker that renders a bounded list of
208
+ * published post previews wherever it appears, so any authored Page (a static theme's own marketing
209
+ * page, or a Page/Post rendered through a `theme.json` `templates` entry) can host a post listing.
210
+ * Same ownership shape as {@link MENU_MARKER_TYPE}/{@link PARTIAL_MARKER_TYPE} immediately above:
211
+ * resolved end-to-end by `features/theme/static-render.ts`'s `injectPostPreviewsEmbeds`, never
212
+ * registered in `widgets/resolver-service.ts`'s `HTML_EMBED_RESOLVERS` — see that file's
213
+ * `THEME_OWNED_MARKER_TYPES`, which carries a matching literal entry for the same disclosed
214
+ * "duplicated, not imported" reason its own doc already states for the other two.
215
+ *
216
+ * Config shape: `{"type":"post-previews","limit":6}` — `limit` is optional (defaults to
217
+ * `DEFAULT_POST_PREVIEWS_LIMIT`, `static-render.ts`) and is always clamped, never trusted verbatim,
218
+ * before it reaches the bounded repo query that feeds this marker.
219
+ */
220
+ export declare const POST_PREVIEWS_MARKER_TYPE = "post-previews";
221
+ /**
222
+ * The collection marker type (2026-09-23) — a theme/page marker that renders a bounded, filtered,
223
+ * sorted list of entries from one custom content type wherever it appears. Same ownership shape as
224
+ * {@link MENU_MARKER_TYPE}/{@link PARTIAL_MARKER_TYPE}/{@link POST_PREVIEWS_MARKER_TYPE} immediately
225
+ * above: resolved end-to-end by `features/theme/static-render.ts`'s `injectCollectionEmbeds`, never
226
+ * registered in `widgets/resolver-service.ts`'s `HTML_EMBED_RESOLVERS` — see that file's
227
+ * `THEME_OWNED_MARKER_TYPES`, which carries a matching literal entry for the same disclosed
228
+ * "duplicated, not imported" reason its own doc gives for the other three.
229
+ *
230
+ * Config shape (validated by `entries/public-list.ts`'s `parseCollectionListConfig`, not by this
231
+ * module): `{"type":"collection","id":"recipe", …}` (`typeKey` accepted as an alias) plus the type's own filter/sort/limit/layout
232
+ * keys. Unlike {@link POST_PREVIEWS_MARKER_TYPE}, a resolved collection marker's wrapper is stripped
233
+ * of `data-embed-config` on the hit path ({@link withInnerContentFinal}) so a later re-scan of the
234
+ * same output can never rediscover and re-resolve it.
235
+ */
236
+ export declare const COLLECTION_MARKER_TYPE = "collection";
237
+ /**
238
+ * The featured-image marker type (2026-10-05) — a post template's slot for the current post's
239
+ * featured image. A static-tier theme has no template language to read `post.featuredImage` (the
240
+ * Liquid/Handlebars tiers' theme data), so before this marker a static theme had no way to show the
241
+ * image at all; it only reached `og:image`. Same ownership shape as {@link COLLECTION_MARKER_TYPE}:
242
+ * resolved end-to-end by `features/theme/static-render.ts`'s `injectFeaturedImage`, listed in
243
+ * `resolver-service.ts`'s `THEME_OWNED_MARKER_TYPES`.
244
+ *
245
+ * Config shape: `{"type":"featured-image"}` — no target; it always means the entity the route is
246
+ * rendering. A hit fills the authored element with the `<img>` and strips the marker; no image
247
+ * (none set, unresolved, or a page with no current post) removes the WHOLE element, wrapper and
248
+ * fallback included, so a theme's figure/margins never render around nothing.
249
+ */
250
+ export declare const FEATURED_IMAGE_MARKER_TYPE = "featured-image";
251
+ /**
252
+ * One marker type's `data-embed-config` keys that can name its resolution target, in the exact
253
+ * precedence order the server tries them — `embedMarkerTarget` returns the FIRST of these keys with a
254
+ * usable string value. This is the "which key names the target" rule Bug A's root cause report
255
+ * (2026-09-23 interactive-bugs plan) found spread across four different resolvers
256
+ * (`resolver-service.ts`'s widget/media/post/content, `routes/site/pages.ts`'s collection) with no
257
+ * single shared table — the admin's own placeholder describer could not reuse it because nothing here
258
+ * exposed it. Hoisting it as data (not a switch statement) is what lets both the server's own resolvers
259
+ * and the admin's UI-only describer read the identical precedence rule.
260
+ *
261
+ * - `widget`/`form`/`media`/`post`/`content`: `id`, else `slug` — registered in
262
+ * `resolver-service.ts`'s `HTML_EMBED_RESOLVERS` (see `marker-target-parity.unit.test.ts`, which fails
263
+ * loudly if a registered type has no matching row or incorrect target keys).
264
+ * - `taxonomy`: `id` only — `widgets/resolvers/taxonomy.ts` reads `ref.id`, accepting a real
265
+ * taxonomy id or a unique taxonomy name through that key; `ref.slug` is never consulted.
266
+ * - {@link COLLECTION_MARKER_TYPE}: `id`, else `typeKey` — `routes/site/pages.ts`'s
267
+ * `collectionMarkerTypeKey` tries `config.id` before `config.typeKey`; `slug` is never consulted.
268
+ * - {@link MENU_MARKER_TYPE} / {@link PARTIAL_MARKER_TYPE}: `id` only — `static-render.ts` resolves
269
+ * both purely by id.
270
+ * - {@link POST_PREVIEWS_MARKER_TYPE}: no keys at all — this marker needs no target
271
+ * ({@link embedMarkerTarget} reports that as its own `{ key: "none" }` outcome, never `undefined`,
272
+ * which means "target missing").
273
+ *
274
+ * An unknown type (not a key of this table) is not a member of it; {@link embedMarkerTarget} falls back
275
+ * to `["id"]` for that case rather than treating an unrecognized type as needing no target — a display
276
+ * must never claim a slug resolves for a type no resolver has ever registered.
277
+ */
278
+ export declare const EMBED_MARKER_TARGET_KEYS: Readonly<Record<string, readonly ("id" | "slug" | "typeKey")[]>>;
279
+ /**
280
+ * One marker's resolved target, from {@link embedMarkerTarget}: either a key/value pair naming what
281
+ * the marker points at, or the `"none"` marker for a type that needs no target at all. Kept distinct
282
+ * from `undefined` (target missing) — see {@link embedMarkerTarget}'s own doc.
283
+ */
284
+ export type EmbedMarkerTarget = {
285
+ readonly key: "id" | "slug" | "typeKey";
286
+ readonly value: string;
287
+ } | {
288
+ readonly key: "none";
289
+ };
290
+ /**
291
+ * Which `data-embed-config` key names `type`'s resolution target, and that key's value, per
292
+ * {@link EMBED_MARKER_TARGET_KEYS}'s precedence order. Three distinct outcomes:
293
+ *
294
+ * - `{ key, value }` — the first target key present with a usable string value.
295
+ * - `{ key: "none" }` — this type ({@link POST_PREVIEWS_MARKER_TYPE}) needs no target; a caller must
296
+ * never read this as "target missing".
297
+ * - `undefined` — the type has one or more target keys, but the marker's config supplies none of them
298
+ * (or only with an unusable value per {@link normalizeTargetValue}) — "target missing".
299
+ *
300
+ * `type` is compared lower-cased, matching `toEmbedRef`'s own `marker.type.toLowerCase()` in
301
+ * `resolver-service.ts` — a hand-authored `"Widget"` must resolve exactly as `"widget"` does.
302
+ *
303
+ * @complexity O(k) in the number of candidate keys for `type` (at most 2 today) — independent of the
304
+ * surrounding document's size.
305
+ */
306
+ export declare function embedMarkerTarget({ type, config }: {
307
+ type: string;
308
+ config: Readonly<Record<string, unknown>>;
309
+ }, _optional?: Record<string, never>): EmbedMarkerTarget | undefined;
310
+ /**
311
+ * The "Copy embed code" snippet for any marker type: the same `<div data-embed-config='{"type":…,
312
+ * "<key>":"<value>"}'></div>` wrapper {@link scanEmbedMarkers}/{@link parseEmbedMarkerConfig} already
313
+ * parse back — generalised (readable-slugs S5b) out of `apps/admin/src/features/collections/rules.ts`'s
314
+ * `collectionEmbedSnippet`, the one admin-generated marker that existed before this: that function is
315
+ * now a one-line call onto this (`embedMarkerSnippet("collection", "id", key)`), byte-for-byte
316
+ * unchanged. `features/media`'s own "Copy embed code" button (S5a) is the second caller, with
317
+ * `("media", "slug", asset.slug)` — see {@link EMBED_MARKER_TARGET_KEYS}'s `media` row for why `slug`
318
+ * is a real, resolvable target key for this type.
319
+ *
320
+ * No escaping beyond the plain template: `key`/`value` are expected to already be validated,
321
+ * quote-free identifiers (a slug, a type key, an id) by the time a caller reaches for a copy button —
322
+ * the same assumption `collectionEmbedSnippet` always made.
323
+ *
324
+ * @complexity O(1) — one template string.
325
+ */
326
+ export declare function embedMarkerSnippet({ type, key, value }: {
327
+ type: string;
328
+ key: "id" | "slug" | "typeKey";
329
+ value: string;
330
+ }, _optional?: Record<string, never>): string;
331
+ /**
332
+ * Rebuild a marker's element around new inner content, keeping its own tag and every authored
333
+ * attribute (`class`, `aria-label`, …). The counterpart to a wholesale replace: a menu marker keeps
334
+ * its `<nav class="docs-nav">` wrapper and only swaps what's inside, whereas a partial slot marker
335
+ * disappears entirely and is replaced by the partial.
336
+ *
337
+ * Synthesizing the open tag from `config` alone instead of using this is a silent bug — it drops the
338
+ * theme's styling hooks and accessible names with nothing failing.
339
+ */
340
+ export declare function withInnerContent({ marker, inner }: {
341
+ marker: EmbedMarker;
342
+ inner: string;
343
+ }, _optional?: Record<string, never>): string;
344
+ /**
345
+ * Rebuild a marker's element with `id` added to its `data-embed-config`, keeping every other
346
+ * authored config key, the marker's own tag, its other attributes, and its authored inner content
347
+ * ({@link EmbedMarker.inner}) completely unchanged. The counterpart to {@link withInnerContent} for a
348
+ * caller that needs to SUPPLY a marker's target rather than its content — e.g. the unified `content`
349
+ * marker (2026-08-11): a theme-authored `{"type":"content"}` with no `id` means "the entity the route
350
+ * already resolved", and the route fills that id in here before the marker is ever resolved, so every
351
+ * downstream consumer (the scanner, the resolver, a later re-scan) sees an ordinary id-carrying marker
352
+ * rather than needing its own "no id means current entity" special case.
353
+ *
354
+ * Callers must check `marker.id === undefined` themselves before calling this — an author's own
355
+ * explicit id always wins over a caller-supplied default, and this function does not re-check that
356
+ * (it would silently overwrite an explicit reference otherwise, which is the one thing the unified
357
+ * marker's id-vs-no-id contract must never do).
358
+ *
359
+ * @complexity O(n) over the marker's own attrs length (one JSON stringify of a small object, one
360
+ * regex replace over `attrs`) — independent of the surrounding document's size.
361
+ */
362
+ export declare function withAddedId({ marker, id }: {
363
+ marker: EmbedMarker;
364
+ id: string;
365
+ }, _optional?: Record<string, never>): string;
366
+ /**
367
+ * Rebuild a marker's element around new inner content, like {@link withInnerContent}, but ALSO strips
368
+ * the marker's own `data-embed-config` attribute from the rebuilt tag — every other authored attribute
369
+ * (`class`, `aria-label`, …) survives, only the marker-ness is removed.
370
+ *
371
+ * For a caller that performs its OWN final resolution outside the shared registry/render pipeline
372
+ * (`pages.ts`'s recursive `content`-marker pre-splice, which fetches and inlines an `"html"`-format
373
+ * entity's own body ahead of the normal `resolveHtmlPageEmbeds`/`renderHtmlPageBody` pass) and must
374
+ * guarantee the result is not rediscovered and re-resolved by a LATER marker scan over the same output
375
+ * — an ordinary {@link withInnerContent} splice would leave `data-embed-config` intact, and the next
376
+ * scan would see what looks like a fresh, unresolved marker and try to resolve it again.
377
+ *
378
+ * @complexity O(n) over the marker's own attrs length (one regex replace) — independent of the
379
+ * surrounding document's size.
380
+ */
381
+ export declare function withInnerContentFinal({ marker, inner }: {
382
+ marker: EmbedMarker;
383
+ inner: string;
384
+ }, _optional?: Record<string, never>): string;
385
+ /**
386
+ * Replace each marker's WHOLE element with what `resolve` returns for it.
387
+ *
388
+ * Whole-element rather than inner-content because the two consumers genuinely differ: a partial slot
389
+ * marker is scaffolding that vanishes once the partial is spliced in, while a menu marker is real
390
+ * theme markup that must survive with only its contents swapped. Callers in the second camp wrap
391
+ * their output in {@link withInnerContent}; making that explicit at the call site is better than a
392
+ * mode flag, because the choice is a real per-type decision and not a preference.
393
+ *
394
+ * `resolve` returning `undefined` means "leave this marker exactly as authored", and that default is
395
+ * load-bearing: a menu that does not exist, a target that was deleted, or a render that produces
396
+ * nothing must fall back to the theme's authored content rather than blanking a nav. Every consumer
397
+ * wants that rule, so it lives here rather than being re-implemented per call site.
398
+ *
399
+ * Applies right-to-left so each splice leaves the earlier markers' offsets valid.
400
+ */
401
+ export declare function substituteMarkers({ html, resolve }: {
402
+ html: string;
403
+ resolve: (marker: EmbedMarker) => string | undefined;
404
+ }, _optional?: Record<string, never>): string;
405
+ /**
406
+ * One HTML attribute authored directly on a marker element (2026-09-16 — owner: "regular attributes
407
+ * that somebody puts on a data-embed-config should work on the rendered tag"). Produced by
408
+ * {@link parseMarkerAttributes}, consumed by callers that forward some or all of a marker's authored
409
+ * attributes onto whatever it resolves to.
410
+ */
411
+ export interface MarkerAttribute {
412
+ readonly name: string;
413
+ /** Source text, exactly as authored — never entity-decoded (see {@link parseMarkerAttributes}'s
414
+ * own doc for why). `null` means the attribute was written with no `="..."` at all (a boolean
415
+ * attribute like a bare `autoplay`), not that it had an empty value. */
416
+ readonly value: string | null;
417
+ }
418
+ /**
419
+ * Parses a marker's verbatim {@link EmbedMarker.attrs} text into structured attributes, in authored
420
+ * order, with `data-embed-config` itself removed (it is the marker, not an attribute a caller would
421
+ * ever want to forward) — the read half of the 2026-09-16 embed-attributes contract:
422
+ * {@link formatMarkerAttributes} is its inverse.
423
+ *
424
+ * - Names are lowercased (HTML attribute names are case-insensitive).
425
+ * - A token whose (lowercased) name is not a valid attribute name per
426
+ * {@link MARKER_ATTRIBUTE_NAME_PATTERN} is skipped, never thrown.
427
+ * - A duplicate name keeps its FIRST occurrence — the same rule a real HTML parser applies, so this
428
+ * parser's result matches what a browser would actually expose on the element.
429
+ * - Every value is kept as SOURCE TEXT: never entity-decoded. Re-decoding `&amp;` here and
430
+ * re-escaping on output (see {@link formatMarkerAttributes}) would double-escape a value like
431
+ * `title="a &amp; b"` into `&amp;amp;` — this module never round-trips through a decoded form.
432
+ *
433
+ * @complexity O(n) in the marker's own attrs length (one regex pass) — independent of the surrounding
434
+ * document's size.
435
+ */
436
+ export declare function parseMarkerAttributes({ marker }: {
437
+ marker: EmbedMarker;
438
+ }, _optional?: Record<string, never>): MarkerAttribute[];
439
+ /**
440
+ * Formats parsed attributes back into attribute-string text, the inverse of
441
+ * {@link parseMarkerAttributes}: a valueless attribute is emitted as a bare ` name`; a valued one as
442
+ * ` name="value"`. The ONLY transform applied to a value is `"` -> `&quot;` (a value may itself
443
+ * contain a literal `"`, e.g. carried over from a single-quoted authored attribute) — `&` is never
444
+ * touched, so source text that already reads `&amp;` is emitted unchanged rather than
445
+ * double-escaping into `&amp;amp;`.
446
+ *
447
+ * @complexity O(n) in the total length of every attribute's name and value.
448
+ */
449
+ export declare function formatMarkerAttributes({ attrs }: {
450
+ attrs: readonly MarkerAttribute[];
451
+ }, _optional?: Record<string, never>): string;
452
+ /** Whether a marker carries any authored attribute besides `data-embed-config` itself — the "bare
453
+ * marker" test {@link withElementKeptIfAttributed} uses to decide whether an element survives a
454
+ * whole-marker substitution.
455
+ *
456
+ * @complexity O(n) in the marker's own attrs length (delegates to {@link parseMarkerAttributes}).
457
+ */
458
+ export declare function hasAuthoredAttributes({ marker }: {
459
+ marker: EmbedMarker;
460
+ }, _optional?: Record<string, never>): boolean;
461
+ /**
462
+ * Rebuild a marker's element around new inner content using an EXPLICIT attribute list (rather than
463
+ * the marker's own verbatim {@link EmbedMarker.attrs}) — for a caller that has partitioned a marker's
464
+ * authored attributes and needs only a subset on the rebuilt wrapper (e.g. {@link substituteHtmlEmbeds}
465
+ * moving media-element attribute names off a non-media wrapper onto the resolved `<video>`/`<img>`,
466
+ * keeping the rest on the wrapper). The counterpart to {@link withInnerContent}, which always keeps
467
+ * every authored attribute verbatim.
468
+ */
469
+ export declare function withInnerContentAndAttributes({ marker, attrs, inner }: {
470
+ marker: EmbedMarker;
471
+ attrs: readonly MarkerAttribute[];
472
+ inner: string;
473
+ }, _optional?: Record<string, never>): string;
474
+ /**
475
+ * Whole-element replace UNLESS the marker carries authored attributes, in which case its element
476
+ * survives (minus `data-embed-config`) around the resolved content — the 2026-09-16 rule for marker
477
+ * types (`widget`, `partial`) that default to disappearing entirely: a bare marker keeps disappearing,
478
+ * byte-identical to before, while an attributed one keeps whatever styling/accessibility hook the
479
+ * author put on it. Delegates to {@link withInnerContentFinal} for the attributed case so the kept
480
+ * wrapper's attribute text is the marker's own verbatim {@link EmbedMarker.attrs} (not a
481
+ * parse-then-reformat round trip) — the same byte-identical-when-nothing-moves discipline
482
+ * {@link withInnerContentFinal} itself already provides.
483
+ */
484
+ export declare function withElementKeptIfAttributed({ marker, inner }: {
485
+ marker: EmbedMarker;
486
+ inner: string;
487
+ }, _optional?: Record<string, never>): string;
488
+ /**
489
+ * Human-readable one-liner for a rejection, for a warning line or a write-time validation error.
490
+ * Deliberately quotes the offending JSON: the author needs to see what they typed, and the raw text
491
+ * is theme- or admin-authored, never end-user input.
492
+ */
493
+ export declare function describeRejection({ rejection }: {
494
+ rejection: EmbedMarkerRejection;
495
+ }, _optional?: Record<string, never>): string;
496
+ //# sourceMappingURL=marker.d.ts.map