@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,149 @@
1
+ import type { Clock, JsonObject } from "@jini-ai/core/primitives";
2
+ import type { OutboxPort } from "../core/ports.js";
3
+ import type { createEntry, updateEntry, importEntry, toEntryOutbox } from "../entries/index.js";
4
+ import type { registerContentType, toContentTypeOutbox, IndexProvisionerPort } from "../content-types/index.js";
5
+ import type { findMediaByIdOrSlug, getLatestTransformDefinition } from "../media/index.js";
6
+ import type { EntryRefRow } from "./ports.js";
7
+ /** Host-owned post snapshot: only the fields the widgets services read or forward. */
8
+ export interface PostRecord {
9
+ id: string;
10
+ workspaceId: string;
11
+ title: string;
12
+ slug: string;
13
+ bodyJson: JsonObject;
14
+ status: "draft" | "published";
15
+ kind: "post" | "page";
16
+ bodyFormat: "doc" | "html";
17
+ updatedAt: string;
18
+ version: number;
19
+ deletedAt?: string | null;
20
+ publishAt?: string | null;
21
+ templateChoice?: string | null;
22
+ overridesThemePage?: boolean | null;
23
+ ext?: JsonObject;
24
+ }
25
+ /** Host-owned blob content-type lookup; absent sha256 keys mean the blob has not been sniffed yet.
26
+ * Widgets only consumes batch reads; the host retains storage and write ownership. */
27
+ export interface MediaContentTypeStorePort {
28
+ getMany(required: {
29
+ workspaceId: string;
30
+ sha256s: readonly string[];
31
+ }): Promise<Map<string, string>>;
32
+ }
33
+ /** Reads stay host-owned; post writes below receive this same bound repository. */
34
+ export interface PostRepoPort {
35
+ findById(required: {
36
+ workspaceId: string;
37
+ id: string;
38
+ }): Promise<PostRecord | null>;
39
+ findBySlug(required: {
40
+ workspaceId: string;
41
+ slug: string;
42
+ }): Promise<PostRecord | null>;
43
+ }
44
+ export type ForgetRemovedPostFn = (required: {
45
+ workspaceId: string;
46
+ id: string;
47
+ }) => Promise<void>;
48
+ export type BeforeSaveHookPort = (entry: {
49
+ readonly id: string;
50
+ readonly workspaceId: string;
51
+ readonly title: string;
52
+ readonly slug: string;
53
+ readonly status: PostRecord["status"];
54
+ readonly bodyJson: Readonly<Record<string, unknown>>;
55
+ readonly ext: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
56
+ }) => Promise<JsonObject>;
57
+ type PostWriteDeps = {
58
+ repo: PostRepoPort;
59
+ clock: Clock;
60
+ outbox: OutboxPort;
61
+ beforeSaveHook?: BeforeSaveHookPort | undefined;
62
+ };
63
+ type PostAttribution = {
64
+ actorId?: string;
65
+ delegatedByWorkspaceId?: string | null | undefined;
66
+ delegatedById?: string | null | undefined;
67
+ };
68
+ /** Required host behavior. No sibling-domain value enters the widgets runtime closure. */
69
+ export interface WidgetHostPorts {
70
+ entries: {
71
+ createEntry: typeof createEntry;
72
+ updateEntry: typeof updateEntry;
73
+ importEntry: typeof importEntry;
74
+ toEntryOutbox: typeof toEntryOutbox;
75
+ isVersionConflict(error: unknown): boolean;
76
+ };
77
+ contentTypes: {
78
+ registerContentType: typeof registerContentType;
79
+ toContentTypeOutbox: typeof toContentTypeOutbox;
80
+ createIndexProvisioner(): IndexProvisionerPort;
81
+ alreadyExists(error: unknown): {
82
+ tombstoned: boolean;
83
+ } | null;
84
+ };
85
+ posts: {
86
+ isTrashed(post: PostRecord): boolean;
87
+ isVersionConflict(error: unknown): error is Error & {
88
+ currentVersion: number;
89
+ };
90
+ isNotFound(error: unknown): boolean;
91
+ updatePost(required: {
92
+ deps: PostWriteDeps;
93
+ input: PostAttribution & {
94
+ workspaceId: string;
95
+ id: string;
96
+ title: string;
97
+ slug: string;
98
+ status: PostRecord["status"];
99
+ bodyJson: JsonObject;
100
+ expectedVersion: number;
101
+ };
102
+ }, optional?: Record<string, never>): Promise<{
103
+ post: PostRecord;
104
+ }>;
105
+ restorePostForward(required: {
106
+ deps: PostWriteDeps & {
107
+ forgetRemoved: ForgetRemovedPostFn;
108
+ };
109
+ input: PostAttribution & {
110
+ prior: PostRecord;
111
+ };
112
+ }, optional?: Record<string, never>): Promise<unknown>;
113
+ findPublishedPostById(required: {
114
+ deps: {
115
+ repo: PostRepoPort;
116
+ };
117
+ input: {
118
+ workspaceId: string;
119
+ id: string;
120
+ };
121
+ }, optional?: Record<string, never>): Promise<PostRecord | null>;
122
+ findPublishedPostBySlug(required: {
123
+ deps: {
124
+ repo: PostRepoPort;
125
+ };
126
+ input: {
127
+ workspaceId: string;
128
+ slug: string;
129
+ };
130
+ }, optional?: Record<string, never>): Promise<PostRecord | null>;
131
+ };
132
+ media: {
133
+ findMediaByIdOrSlug: typeof findMediaByIdOrSlug;
134
+ getLatestTransformDefinition: typeof getLatestTransformDefinition;
135
+ publicTransformName: string;
136
+ };
137
+ entryRefs: {
138
+ extract(required: {
139
+ workspaceId: string;
140
+ sourceEntryId: string;
141
+ sourceEntryType: string;
142
+ bodyJson: unknown;
143
+ fieldsExt: Record<string, unknown>;
144
+ }, optional?: Record<string, never>): readonly EntryRefRow[];
145
+ };
146
+ slugify(text: string): string;
147
+ }
148
+ export {};
149
+ //# sourceMappingURL=host-ports.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=host-ports.js.map
@@ -0,0 +1,247 @@
1
+ /**
2
+ * @file The embed-reference view of a `"html"`-format Page's body (SPEC-047 Slice 2, generalized
3
+ * 2026-08-07 per `project-tovu-generic-embed-contract`, rewired onto the shared marker parser
4
+ * 2026-08-10).
5
+ *
6
+ * Purpose:
7
+ * A TipTap `widgetEmbed` node has a structured home (`bodyJson`) to carry a `{placementId,
8
+ * widgetEntryId}` attrs object. A Page's body is a free-form HTML string with no such tree, so an
9
+ * embed there has to be a literal markup convention instead:
10
+ * `<div data-embed-config='{"type":"widget","id":"{widgetEntryId}"}'></div>` (or `"type":"media"`,
11
+ * `"post"`, or any future type token), anywhere in the page's `body_html`.
12
+ *
13
+ * **This file no longer owns a pattern.** Locating and parsing a marker is `core/embeds/marker.ts`'s
14
+ * single job; this module is only the adapter that projects a parsed marker into the
15
+ * {@link PageHtmlEmbedRef} shape `resolver-service.ts` resolves against. Before the 2026-08-10
16
+ * unification there were four regexes for one concept — this one, `static-render.ts`'s,
17
+ * `entry-refs/extractor.ts`'s deliberate second copy, and the theme-slot pair — and they had already
18
+ * drifted: this one required an EMPTY `<div></div>`, while the theme path deliberately matched a
19
+ * marker WITH authored fallback content inside it. Sharing the parser is what makes "what render.ts
20
+ * embeds" and "what entry_refs indexes" incapable of disagreeing, rather than merely tested for
21
+ * agreement after the fact.
22
+ *
23
+ * **Why `type` is a free string, not a closed union.** A closed union would mean this file's own
24
+ * type signature has to change every time a new embed type is added — exactly the coupling the
25
+ * generic contract exists to remove. Adding a fourth type is a new resolver registration
26
+ * (`resolver-service.ts`'s `HTML_EMBED_RESOLVERS`), never a scanner change. Unknown types flow
27
+ * through untouched; "is this type known" is a resolution-time question, never a scan-time one.
28
+ *
29
+ * **Inner content is now allowed, and that is a widening.** The old empty-div requirement meant any
30
+ * content inside a placeholder kept it from matching at all — it rendered verbatim and inert. The
31
+ * shared parser captures inner content instead, because a theme marker's authored content is a real
32
+ * fallback that must survive when nothing resolves. For most types the consequence is that
33
+ * {@link substituteHtmlEmbeds} replaces the WHOLE element, fallback content included, exactly as it
34
+ * already replaced the whole empty div: an html Page's embed placeholder is scaffolding for the
35
+ * resolved widget, not a nav that must survive a failed lookup. Unresolved still degrades to the
36
+ * REQ-28 placeholder via the caller's own `resolve`, never to a crash and never to raw marker markup
37
+ * reaching a visitor.
38
+ *
39
+ * **`"media"`, `"post"`, and `"content"` are wrapper-preserving.** An author who wraps one of these
40
+ * markers in their own element — `<div style="max-width:600px"
41
+ * data-embed-config='{"type":"media","id":"..."}'></div>`, or `<main class="page-body"
42
+ * data-embed-config='{"type":"content","id":"..."}'></main>` — expects that element and its own
43
+ * attributes (style, class, id, aria-*) to survive; only the marker's CONTENT should become the
44
+ * resolved markup. See {@link WRAPPER_PRESERVING_EMBED_TYPES}'s own doc for the per-type reasoning,
45
+ * and {@link substituteHtmlEmbeds}'s own doc for the one tag-nesting exception `"media"` alone needs.
46
+ *
47
+ * **`"widget"` (and any other type this stage resolves) keeps its element ONLY if the author gave it
48
+ * attributes (2026-09-16, direct owner ask: "regular attributes that somebody puts on a
49
+ * data-embed-config should work on the rendered tag").** A BARE `<div
50
+ * data-embed-config='{"type":"widget","id":"w1"}'></div>` is still whole-element replaced,
51
+ * byte-identical to before — `"widget"`'s resolved root tag is not a small, closed set (see
52
+ * {@link WRAPPER_PRESERVING_EMBED_TYPES}'s own doc for why it still cannot unconditionally join the
53
+ * wrapper-preserving group), but an author who wrote `<div style="max-width:600px"
54
+ * data-embed-config='{"type":"widget","id":"w1"}'></div>` plainly wants that `style` to survive.
55
+ * {@link withElementKeptIfAttributed} is the shared primitive for this rule.
56
+ *
57
+ * **`"media"`'s own attributes (2026-09-16) are partitioned, not simply kept-or-dropped.** See
58
+ * {@link MEDIA_ELEMENT_ATTRIBUTE_NAMES} and {@link substituteHtmlEmbeds}'s own doc.
59
+ *
60
+ * **Why attribute VALUES are not re-validated here for injection safety.** A Page's `body_html` is
61
+ * already unescaped, trusted-ish markup by the time it reaches this module (see
62
+ * `update-html.ts`'s own disclosure) — nothing this file does widens that trust boundary. The one
63
+ * thing this module owns defensively is bounding how much work an adversarial/oversized page can
64
+ * demand at render/index time (`MAX_HTML_EMBEDS_PER_PAGE`) and never using a captured id for
65
+ * anything other than a map/repo lookup key (never re-emitted into HTML unescaped, never
66
+ * interpolated into a query) — see `resolver-service.ts`'s `resolveHtmlPageEmbeds` and
67
+ * `extractor.ts`'s html-ref collector, both of which only ever use the id that way.
68
+ *
69
+ * **`slug`, an author-memorable alternative to `id` (2026-08-31).** The owner's own complaint —
70
+ * `{"type":"widget","id":"29721c44-a811-444f-b7b5-e61a9918a3a9"}` is not something a person can
71
+ * type from memory — is real, and this is the fix: `{"type":"widget","slug":"contact-form"}`.
72
+ * `slug` is projected here exactly like `name`/`variant` already are (a raw, unvalidated config
73
+ * key; deciding whether it is USABLE is a resolver-side concern), but it is a genuinely different
74
+ * mechanism from the dormant `name` field above, not a rename of it. `name` was scaffolded
75
+ * (2026-08-05/07 generic-embed-contract design, `project-tovu-generic-embed-contract` in
76
+ * `ADS-memory/`) for a fuzzy, POSSIBLY-AMBIGUOUS match — "exactly one match resolves; zero or >1
77
+ * degrades to the placeholder, never a guess" — and no resolver in this codebase has ever
78
+ * implemented that ambiguity guard. `slug` needs no such guard: it targets a column
79
+ * (`entries.slug`) carrying a real database `UNIQUE(workspace_id, type, slug)` index, so "more than
80
+ * one match" cannot occur by construction, not merely by convention. Reusing `name`'s never-shipped,
81
+ * ambiguity-hedged semantics for a value that is unique by database constraint would be modeling a
82
+ * problem this feature does not have.
83
+ *
84
+ * **`id` stays authoritative.** When a marker carries both, `id` wins and `slug` is not even
85
+ * consulted — this is a strict widening of the pre-existing "the id resolves" rule (a marker with no
86
+ * `slug` key behaves byte-identically to before), not a new fallback CHAIN where a present-but-broken
87
+ * `id` triggers a `slug` retry; nothing in this codebase's history committed to that stronger contract
88
+ * and building it would add a second resolution path for no requirement in hand. See
89
+ * `resolver-service.ts`'s `resolveWidgetTypeEmbeds` for where `slug` is actually resolved — today
90
+ * that is `"widget"` only (`entries.slug` is unique per `(workspaceId, type)`; `posts.slug` is
91
+ * equally unique and could gain the identical treatment cheaply in a follow-up, but is intentionally
92
+ * left untouched by this change; `media` has no `slug` column at all and cannot support this without
93
+ * a schema migration).
94
+ */
95
+ import { type MarkerAttribute } from "../markers/marker.js";
96
+ /**
97
+ * One embed reference found in a Page's `body_html`. `type` is deliberately a free string (see this
98
+ * file's header) — the scanner never validates it against a known-type list. `id`/`slug`/`name`/
99
+ * `variant` are `null` when the corresponding CONFIG KEY is absent or not a string (or, for `id`/
100
+ * `slug`, out of {@link MAX_EMBED_ID_LENGTH} bounds) — the scanner reports what is literally written
101
+ * in the markup; deciding whether an absent/invalid key makes the reference resolvable is a
102
+ * resolver-side concern (`resolver-service.ts`), not a scanner-side one.
103
+ *
104
+ * Kept as a narrow projection of `EmbedMarker.config` rather than replaced by it: every resolver in
105
+ * `HTML_EMBED_RESOLVERS` reads exactly these five fields, and `null`-for-absent is the shape their
106
+ * skip branches already test. A resolver needing a new key reads it off the marker config directly
107
+ * — this interface is the compatibility seam, not a ceiling.
108
+ */
109
+ export interface PageHtmlEmbedRef {
110
+ readonly type: string;
111
+ readonly id: string | null;
112
+ /** See this file's header for why this is NOT the same mechanism as `name` below, despite the
113
+ * similar shape — `slug` targets a database-unique column, `name` a never-implemented fuzzy match. */
114
+ readonly slug: string | null;
115
+ readonly name: string | null;
116
+ readonly variant: string | null;
117
+ /**
118
+ * `false` only when the config key is the literal JSON boolean `false`; every other case
119
+ * (absent, `true`, or any non-boolean value) is `true` (2026-09-04). Today read only by the
120
+ * `"content"` resolver (`resolver-service.ts`'s `resolveContentTypeEmbeds`), which threads it
121
+ * into the `"post-content"` IR's `props.header` so `render.ts`'s `renderWidgetPostContent` can
122
+ * skip its `.post-detail-header` (`<h1>` + date byline) wrapper — previously unconditional
123
+ * application code a template had no way to opt out of, forcing a `display:none` CSS workaround
124
+ * for a homepage doc-format row that does not want a dated byline. `true`-by-default is load-
125
+ * bearing: no existing template (a `kind:"post"` detail page, the listing page, `about`, `docs`)
126
+ * carries a `header` key, and this default is what keeps every one of them rendering byte-
127
+ * identically to before this field existed. Not read by `"widget"`/`"media"`/the legacy `"post"`
128
+ * type — this is a `"content"`-marker-specific concern, not a general marker attribute.
129
+ */
130
+ readonly header: boolean;
131
+ readonly mode?: "html";
132
+ }
133
+ /**
134
+ * Resource bound (Programmer workflow step 5a4) mirroring `embed-service.ts`'s
135
+ * `DEFAULT_MAX_EMBEDS_PER_DOCUMENT` for the TipTap path — same policy, same value, applied here
136
+ * because an html Page has no equivalent write-time guardrail (`PagesHtmlDocumentStore.write()`
137
+ * accepts any string; `embed-validation.ts`'s `validateWidgetEmbedMutation` is never in this path).
138
+ * Without a cap, a page carrying thousands of embed placeholders would grow `entry_refs` and the
139
+ * render-time resolved-id set unboundedly. `scanHtmlEmbeds` truncates AT this many refs; occurrences
140
+ * beyond the cap simply resolve to the same REQ-28 placeholder every other unresolved reference
141
+ * does (see `substituteHtmlEmbeds`'s own doc for why it does not need a matching count check).
142
+ *
143
+ * **This is a render/index-time resource guard, NOT a write-time invariant.** Nothing stops
144
+ * `pages_write_html`/the admin HTML editor from storing a page with 5,000 embed placeholders in
145
+ * `body_html` — `PagesHtmlDocumentStore.write()` has no count check of its own, unlike
146
+ * `embed-validation.ts`'s `validateWidgetEmbedMutation` (REQ-20) on the TipTap path, which rejects
147
+ * an over-limit mutation before it is ever persisted. This constant only bounds how much of an
148
+ * already-stored oversized page gets resolved/indexed on any single render or reindex pass — it
149
+ * does not guarantee such a page can never exist. A write-time cap would need its own design
150
+ * decision (reject vs. truncate vs. warn the operator) that has not been made; do not read this
151
+ * constant as implying one already has been.
152
+ */
153
+ export declare const MAX_HTML_EMBEDS_PER_PAGE = 50;
154
+ /**
155
+ * Every embed reference in `html`, in document order, truncated at
156
+ * {@link MAX_HTML_EMBEDS_PER_PAGE}. Pure — no I/O, never throws. Shared by the render-time resolver
157
+ * (`resolver-service.ts`'s `resolveHtmlPageEmbeds`, which needs to know what to batch-load) and
158
+ * anything that needs to enumerate a page's embed set without rendering it.
159
+ *
160
+ * A marker whose config does not parse is simply absent from the result — render-time stays
161
+ * forgiving (warn + degrade, never throw), and the unparseable marker's own markup survives
162
+ * untouched into the output because {@link substituteHtmlEmbeds} cannot see it either. The LOUD
163
+ * treatment of `rejected` belongs to `core/entry-refs/extractor.ts` (the integrity path a dropped
164
+ * reference would let a delete proceed against) and to the write chokepoint, not here.
165
+ *
166
+ * @complexity O(n) over `html`'s length for the shared scan, plus O(min(k, cap)) to build the
167
+ * returned array, where k is the number of markers.
168
+ * @overallScore 100
169
+ */
170
+ export declare function scanHtmlEmbeds({ html }: {
171
+ html: string;
172
+ }, _optional?: Record<string, never>): PageHtmlEmbedRef[];
173
+ /**
174
+ * Attribute names D2 (2026-09-16 embed-attributes plan) says mean something on a rendered
175
+ * `<video>`/`<img>` and NOTHING on a `<div>`/other wrapper tag — so when a `"media"` marker is
176
+ * authored on a non-self-rendering wrapper, an attribute in this set moves off the wrapper and onto
177
+ * the resolved media element; every other authored attribute (`class`, `style`, `id`, `data-*`,
178
+ * `aria-*`, an unknown name) stays on the wrapper. See {@link substituteHtmlEmbeds}'s own doc for how
179
+ * this partition is used, and `render.ts`'s merge helper for how `EmbedOccurrence.elementAttributes`
180
+ * actually lands on the rendered tag (D3's precedence rules).
181
+ *
182
+ * `controls` is listed here (it means nothing on a `<div>` either) even though it is ALSO read
183
+ * separately into {@link EmbedOccurrence.controls} — see {@link resolveMarkerControls}'s own doc for
184
+ * why a raw `controls` attribute is never itself re-emitted onto the rendered element.
185
+ */
186
+ export declare const MEDIA_ELEMENT_ATTRIBUTE_NAMES: ReadonlySet<string>;
187
+ /**
188
+ * One marker occurrence's forwarded attribute data (2026-09-16 embed-attributes plan), handed to
189
+ * `resolve` alongside its {@link PageHtmlEmbedRef} — the per-occurrence half of "attributes on the
190
+ * marker element reach the rendered output" that a `PageHtmlEmbedRef` alone cannot carry (it is keyed
191
+ * by config content, not by which literal marker element authored it; `entry_refs`/the resolver both
192
+ * intentionally do not need presentation data on that shape).
193
+ */
194
+ export interface EmbedOccurrence {
195
+ /**
196
+ * Only meaningful for `"media"`: on a wrapper, the authored attributes D2
197
+ * ({@link MEDIA_ELEMENT_ATTRIBUTE_NAMES}) says belong on `<video>`/`<img>`, not the wrapper; on a
198
+ * marker authored directly on a self-rendering tag ({@link SELF_RENDERING_MEDIA_TAGS}), every
199
+ * authored attribute. Empty for every other type.
200
+ */
201
+ readonly elementAttributes: readonly MarkerAttribute[];
202
+ /**
203
+ * `false` only when the marker's own `controls` attribute resolves to off — see
204
+ * {@link resolveMarkerControls}. `true` (the pre-existing default) for every non-`"media"` type,
205
+ * where the field is unused.
206
+ */
207
+ readonly controls: boolean;
208
+ }
209
+ /**
210
+ * Replaces every embed placeholder in `html` with `resolve(ref, occurrence)`'s return value.
211
+ * `resolve` is synchronous and pure from this function's own perspective — the caller is expected
212
+ * to have already batch-resolved every ref it cares about (mirroring how `render.ts`'s
213
+ * `renderDocNode` receives an already-resolved `inlineResolved` map rather than doing its own I/O);
214
+ * a ref `resolve` doesn't recognize (unknown type, never loaded, or beyond
215
+ * {@link MAX_HTML_EMBEDS_PER_PAGE}) is `resolve`'s own call to degrade safely, not something this
216
+ * function special-cases — every caller here (`render.ts`'s `renderHtmlPageBody`) already returns
217
+ * the REQ-28 placeholder for an unresolved ref, which is exactly what "no cap-aware branch needed
218
+ * here" relies on.
219
+ *
220
+ * `resolve` returning `undefined` means **leave this marker exactly as authored**, forwarded straight
221
+ * to `substituteMarkers`' own invariant of the same name. That is not a convenience: since the
222
+ * 2026-08-10 unification this stage SEES markers it does not own (a theme's `partial`/`menu`), and a
223
+ * later stage resolves them. Substituting anything at all over one of those — including the REQ-28
224
+ * placeholder — silently deletes a nav. Which types this stage owns is `resolver-service.ts`'s
225
+ * `isPageEmbedType` to answer; this function only carries the answer through.
226
+ *
227
+ * Per-type handling (2026-09-16):
228
+ * - **`"media"`**: {@link planMediaMarkerAttributes} computes the occurrence AND the wrapper split.
229
+ * On the marker's own self-rendering tag ({@link SELF_RENDERING_MEDIA_TAGS}) the replacement is
230
+ * returned as-is (no wrapper, unchanged from before). On any other tag: if nothing moved, the
231
+ * wrapper is rebuilt via the byte-identical {@link withInnerContentFinal} path; otherwise via
232
+ * {@link withInnerContentAndAttributes} with only the reduced wrapper attribute list.
233
+ * - **`"post"`/`"content"`** ({@link WRAPPER_PRESERVING_EMBED_TYPES}): spliced via
234
+ * {@link spliceWrapperPreservingReplacement}, occurrence attributes always empty.
235
+ * - **Every other type** (`"widget"`, and any type this stage does not itself own but still
236
+ * resolves): {@link withElementKeptIfAttributed} — a bare marker keeps the pre-existing
237
+ * whole-element replace; an attributed one keeps its element around the resolved output.
238
+ *
239
+ * @complexity O(n) over `html`'s length for the shared scan, plus O(k) per `"media"` marker in its own
240
+ * attribute count (one two-way partition), O(1) per marker otherwise.
241
+ * @overallScore 100
242
+ */
243
+ export declare function substituteHtmlEmbeds({ html, resolve }: {
244
+ html: string;
245
+ resolve: (ref: PageHtmlEmbedRef, occurrence: EmbedOccurrence) => string | undefined;
246
+ }, _optional?: Record<string, never>): string;
247
+ //# sourceMappingURL=html-embeds.d.ts.map