@sonordev/site-kit 7.0.1 → 7.1.0

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 (147) hide show
  1. package/CHANGELOG.md +3606 -0
  2. package/README.md +12 -13
  3. package/agent-manifest.json +11 -5
  4. package/dist/{AnalyticsProvider-EMM2TKRE.js → AnalyticsProvider-ZMQUV33M.js} +4 -4
  5. package/dist/{ArticleViewTracker-RA64BGL6.js → ArticleViewTracker-V7NUXYBY.js} +3 -3
  6. package/dist/{BlocksPopup-D25RFNOV.js → BlocksPopup-52EU7OUY.js} +4 -4
  7. package/dist/{ChatWidget-RYI7BMJJ.js → ChatWidget-5BHMNR57.js} +5 -5
  8. package/dist/{EngageWidget-UKFCN33M.js → EngageWidget-PCGLX7SO.js} +4 -4
  9. package/dist/{FileField-MUHA7LZR.js → FileField-TSFGNAMY.js} +3 -3
  10. package/dist/{FormSpotlight-TCLPWPLL.js → FormSpotlight-XLBWEOTE.js} +1 -1
  11. package/dist/{FormStage-CNYLP6I6.js → FormStage-IJQ5Q2X6.js} +1 -1
  12. package/dist/{ManagedForm-7ZL5SKTO.js → ManagedForm-Z3PKOPIZ.js} +6 -6
  13. package/dist/{ManagedNewsletterForm-33B4JLX7.js → ManagedNewsletterForm-QAO3POLC.js} +4 -4
  14. package/dist/{SignalCore-L5FVDHFE.js → SignalCore-RBA3VDBL.js} +3 -3
  15. package/dist/{SiteDesignReporter-4JOFL4FP.js → SiteDesignReporter-C4LR5X2V.js} +5 -5
  16. package/dist/SitemapSync-XVMGKCF3.js +8 -0
  17. package/dist/_client/booking-widget.js +5 -5
  18. package/dist/affiliates/index.js +3 -3
  19. package/dist/analytics/index.js +4 -4
  20. package/dist/articles/index.js +1 -1
  21. package/dist/articles/server-ui.js +1 -1
  22. package/dist/chat/index.js +5 -5
  23. package/dist/{chunk-FYBZ5SNP.js → chunk-3G2SE2J4.js} +1 -1
  24. package/dist/{chunk-HGCK465A.js → chunk-3J2ERO3I.js} +1 -1
  25. package/dist/{chunk-KXPBMCFL.js → chunk-3QI26673.js} +3 -1
  26. package/dist/{chunk-BMO3VGMR.js → chunk-3XPJKZ6D.js} +30 -7
  27. package/dist/{chunk-QGHSMJKW.js → chunk-4JQQDCMO.js} +1 -1
  28. package/dist/{chunk-6HDT4G4A.js → chunk-4YTYGG2C.js} +2 -2
  29. package/dist/{chunk-N2UVOR3X.js → chunk-662ILEZ6.js} +2 -0
  30. package/dist/chunk-6G43IRWR.js +4 -0
  31. package/dist/{chunk-LVESVYCE.js → chunk-7MHHWZKC.js} +11 -117
  32. package/dist/{chunk-MV2MBTC3.js → chunk-7QTMMHUO.js} +1 -1
  33. package/dist/{chunk-KPAZG65P.js → chunk-CGWUXUYZ.js} +138 -46
  34. package/dist/{chunk-4IQ52CXL.js → chunk-DUAO4Q75.js} +2 -2
  35. package/dist/{chunk-4RMVXRBO.js → chunk-EIULXXUJ.js} +3 -3
  36. package/dist/{chunk-3KUUH2YP.js → chunk-EVFZ7KEW.js} +1 -1
  37. package/dist/{chunk-OFOAHPUV.js → chunk-F42R35NV.js} +1 -1
  38. package/dist/{chunk-QANVUXKH.js → chunk-FLR3EMK6.js} +1 -1
  39. package/dist/{chunk-P4GRY6QP.js → chunk-GIAOPEN6.js} +1 -1
  40. package/dist/{chunk-P5J7VMQ3.js → chunk-GWUKQ26F.js} +1 -1
  41. package/dist/{chunk-TT63HHIT.js → chunk-HAG4YIZY.js} +1 -1
  42. package/dist/{chunk-QZZIKMAT.js → chunk-HVH37YPX.js} +1 -1
  43. package/dist/{chunk-SSUQKA7L.js → chunk-J4D6ZXRW.js} +1 -1
  44. package/dist/{chunk-GYESATRY.js → chunk-L2DJD5Y4.js} +1 -1
  45. package/dist/{chunk-WATH55UY.js → chunk-LFXVE32I.js} +1 -1
  46. package/dist/chunk-LPH5FANE.js +169 -0
  47. package/dist/{chunk-V6LSQRTH.js → chunk-PLUP2KN5.js} +1 -1
  48. package/dist/chunk-RYVDGXC2.js +19 -0
  49. package/dist/{chunk-EGOD74PP.js → chunk-U35H2JIQ.js} +2 -2
  50. package/dist/chunk-VCJYLYJV.js +49 -0
  51. package/dist/{chunk-FL4EPUWA.js → chunk-W2CL2DB3.js} +2 -2
  52. package/dist/{chunk-UZN4ZYR2.js → chunk-XD3ZQET6.js} +1 -1
  53. package/dist/{chunk-CVTVNC2U.js → chunk-XNVSCQ2O.js} +2 -2
  54. package/dist/{chunk-T3MC4HOD.js → chunk-YLSEB32F.js} +1 -1
  55. package/dist/chunk-ZETJTCMV.js +118 -0
  56. package/dist/{chunk-5SEM2V4A.js → chunk-ZIMFQWGJ.js} +3 -3
  57. package/dist/client/index.js +3 -3
  58. package/dist/cms/CmsPage.d.ts +1 -0
  59. package/dist/cms/CmsPreview.d.ts +1 -0
  60. package/dist/cms/CmsSection.d.ts +1 -0
  61. package/dist/cms/index.d.ts +6 -0
  62. package/dist/cms/server-api.d.ts +3 -0
  63. package/dist/commerce/index.js +4 -4
  64. package/dist/config/index.js +1 -1
  65. package/dist/contracts/entries.d.ts +1 -1
  66. package/dist/contracts/site-cache.d.ts +55 -0
  67. package/dist/contracts/site-edit-param.d.ts +7 -0
  68. package/dist/contracts/site-edit.d.ts +77 -0
  69. package/dist/contracts/slot-content.d.ts +111 -0
  70. package/dist/contracts/slots.d.ts +39 -25
  71. package/dist/engage/index.js +6 -6
  72. package/dist/fleet/index.js +4 -4
  73. package/dist/forms/index.js +8 -8
  74. package/dist/forms/server.js +2 -2
  75. package/dist/forms/types.d.ts +3 -1
  76. package/dist/images/index.js +4 -4
  77. package/dist/index.js +1 -1
  78. package/dist/layout/client.js +8 -7
  79. package/dist/layout/index.js +9 -8
  80. package/dist/llms/index.js +4 -2
  81. package/dist/llms/seo-revalidate.d.ts +8 -1
  82. package/dist/maps/index.js +3 -3
  83. package/dist/mcp/sonor.js +6 -6
  84. package/dist/overlay-RXV6U6QC.js +353 -0
  85. package/dist/proxy/index.js +2 -2
  86. package/dist/proxy/securityHeaders.d.ts +4 -0
  87. package/dist/revalidate/index.d.ts +44 -0
  88. package/dist/revalidate/index.js +27 -0
  89. package/dist/seo/ManagedContent.d.ts +2 -0
  90. package/dist/seo/client.js +4 -4
  91. package/dist/seo/index.js +9 -8
  92. package/dist/seo/llms.js +4 -2
  93. package/dist/seo/register-sitemap-cli.js +1 -1
  94. package/dist/seo/server.js +3 -2
  95. package/dist/seo/sitemap.js +2 -2
  96. package/dist/server/index.js +2 -2
  97. package/dist/{server-api-GJJQZVG7.js → server-api-BVCBLJKL.js} +2 -1
  98. package/dist/shared/build-entries.d.ts +1 -0
  99. package/dist/shared/edit-bridge.d.ts +8 -0
  100. package/dist/shared/version.d.ts +1 -1
  101. package/dist/signal/index.js +2 -2
  102. package/dist/sitemap/index.js +2 -2
  103. package/dist/slots/ManagedLink.d.ts +31 -0
  104. package/dist/slots/ManagedList.d.ts +30 -0
  105. package/dist/slots/ManagedRichText.d.ts +31 -0
  106. package/dist/slots/contract.js +2 -1
  107. package/dist/slots/edit/locate.d.ts +30 -0
  108. package/dist/slots/edit/overlay.d.ts +18 -0
  109. package/dist/slots/index.d.ts +12 -4
  110. package/dist/slots/index.js +4 -2
  111. package/dist/slots/revalidate.d.ts +8 -3
  112. package/dist/slots/rich.d.ts +7 -0
  113. package/dist/slots/server-api.d.ts +6 -2
  114. package/dist/sync/index.js +5 -5
  115. package/dist/website/images.js +4 -4
  116. package/dist/website/index.js +5 -5
  117. package/dist/website/popups.js +4 -4
  118. package/dist/website/slots/contract.js +2 -1
  119. package/dist/website/slots.js +4 -2
  120. package/dist/{writeLLMsTxt-UMHKGNRR.js → writeLLMsTxt-QR23OQUE.js} +1 -1
  121. package/docs/MIGRATING-TO-7.md +146 -0
  122. package/docs.json +69 -0
  123. package/package.json +14 -4
  124. package/src/admin-auth/README.md +88 -0
  125. package/src/analytics/README.md +264 -0
  126. package/src/articles/README.md +325 -0
  127. package/src/commerce/README.md +109 -0
  128. package/src/cta-bar/README.md +154 -0
  129. package/src/engage/README.md +241 -0
  130. package/src/forms/README.md +219 -0
  131. package/src/images/README.md +74 -0
  132. package/src/layout/README.md +66 -0
  133. package/src/llms/README.md +723 -0
  134. package/src/mcp/README.md +376 -0
  135. package/src/motion/README.md +372 -0
  136. package/src/og/README.md +304 -0
  137. package/src/proxy/README.md +152 -0
  138. package/src/redirects/README.md +74 -0
  139. package/src/reputation/README.md +64 -0
  140. package/src/revalidate/README.md +82 -0
  141. package/src/seo/README.md +346 -0
  142. package/src/signal/README.md +115 -0
  143. package/src/sitemap/README.md +127 -0
  144. package/src/slots/README.md +168 -0
  145. package/src/sync/README.md +115 -0
  146. package/dist/SitemapSync-7WKY4HXI.js +0 -8
  147. package/dist/chunk-SS636UDN.js +0 -35
@@ -0,0 +1,169 @@
1
+ // src/contracts/slot-content.ts
2
+ var SLOT_CONTENT_TYPES = ["text", "rich", "link", "list"];
3
+ var SLOT_V1_CONTENT_TYPES = ["text"];
4
+ var SLOT_CONTENT_MAX_LENGTH = 1e4;
5
+ var SLOT_LIST_MAX_ITEMS = 50;
6
+ var SLOT_FIELD_MAX_LENGTH = 2e3;
7
+ var SLOT_HREF_MAX_LENGTH = 2048;
8
+ var SLOT_ID_PATTERN = /^[a-z0-9][a-z0-9-_.:]{0,127}$/;
9
+ function isValidSlotId(id) {
10
+ return SLOT_ID_PATTERN.test(id);
11
+ }
12
+ function isSlotContentType(type) {
13
+ return typeof type === "string" && SLOT_CONTENT_TYPES.includes(type);
14
+ }
15
+ function isSafeSlotHref(href) {
16
+ if (typeof href !== "string") return false;
17
+ const h = href.trim();
18
+ if (!h || h.length > SLOT_HREF_MAX_LENGTH) return false;
19
+ if (h.startsWith("/")) return !h.startsWith("//") && !/\s/.test(h);
20
+ if (h.startsWith("#")) return !/\s/.test(h);
21
+ return /^(https?:\/\/[^\s]+|mailto:[^\s]+|tel:[+\d][\d\s().-]*)$/i.test(h);
22
+ }
23
+ function isSafeSlotImageSrc(src) {
24
+ return typeof src === "string" && src.length <= SLOT_HREF_MAX_LENGTH && /^https:\/\/[^\s]+$/i.test(src);
25
+ }
26
+ function cleanField(v) {
27
+ if (typeof v !== "string") return void 0;
28
+ const t = v.trim();
29
+ return t ? t.slice(0, SLOT_FIELD_MAX_LENGTH) : void 0;
30
+ }
31
+ function normalizeSlotValue(type, input) {
32
+ if (type === "text" || type === "rich") {
33
+ const content = typeof input.content === "string" ? input.content.trim() : "";
34
+ if (!content) return { ok: false, error: `${type} slot needs non-empty content` };
35
+ if (content.length > SLOT_CONTENT_MAX_LENGTH) {
36
+ return { ok: false, error: `content is over ${SLOT_CONTENT_MAX_LENGTH} characters` };
37
+ }
38
+ return { ok: true, content, value: null };
39
+ }
40
+ const raw = input.value ?? null;
41
+ if (!raw || typeof raw !== "object") return { ok: false, error: `${type} slot needs a value` };
42
+ if (type === "link") {
43
+ const label = cleanField(raw.label);
44
+ const href = typeof raw.href === "string" ? raw.href.trim() : "";
45
+ if (!label) return { ok: false, error: "link needs a label" };
46
+ if (!isSafeSlotHref(href)) {
47
+ return { ok: false, error: "link href must be https, http, mailto, tel, #anchor or a /path" };
48
+ }
49
+ return { ok: true, content: label, value: { label, href } };
50
+ }
51
+ if (type !== "list") return { ok: false, error: `unknown slot type ${String(type)}` };
52
+ const itemsIn = Array.isArray(raw.items) ? raw.items : null;
53
+ if (!itemsIn) return { ok: false, error: "list needs items" };
54
+ if (itemsIn.length > SLOT_LIST_MAX_ITEMS) {
55
+ return { ok: false, error: `list is over ${SLOT_LIST_MAX_ITEMS} items` };
56
+ }
57
+ const items = [];
58
+ for (const [i, it] of itemsIn.entries()) {
59
+ if (!it || typeof it !== "object") return { ok: false, error: `item ${i + 1} isn't an object` };
60
+ const src = it;
61
+ const item = {};
62
+ const title = cleanField(src.title);
63
+ const body = cleanField(src.body);
64
+ if (title) item.title = title;
65
+ if (body) item.body = body;
66
+ if (src.href !== void 0 && src.href !== null && src.href !== "") {
67
+ if (!isSafeSlotHref(src.href)) return { ok: false, error: `item ${i + 1} has an unsafe href` };
68
+ item.href = src.href.trim();
69
+ }
70
+ if (src.image !== void 0 && src.image !== null) {
71
+ const img = src.image;
72
+ if (!isSafeSlotImageSrc(img.src)) return { ok: false, error: `item ${i + 1} image needs an https src` };
73
+ const alt = typeof img.alt === "string" ? img.alt.trim().slice(0, SLOT_FIELD_MAX_LENGTH) : "";
74
+ const image = { src: img.src, alt };
75
+ if (Number.isInteger(img.width) && img.width > 0) image.width = img.width;
76
+ if (Number.isInteger(img.height) && img.height > 0) image.height = img.height;
77
+ item.image = image;
78
+ }
79
+ if (!item.title && !item.body && !item.image) {
80
+ return { ok: false, error: `item ${i + 1} needs a title, body or image` };
81
+ }
82
+ items.push(item);
83
+ }
84
+ if (items.length === 0) return { ok: false, error: "list needs at least one item" };
85
+ return { ok: true, content: slotListPlainText(items), value: { items } };
86
+ }
87
+ function slotListPlainText(items) {
88
+ const text = items.map((it) => it.title ?? it.body ?? it.image?.alt ?? "").filter(Boolean).join("\n");
89
+ return (text || `${items.length} items`).slice(0, SLOT_CONTENT_MAX_LENGTH);
90
+ }
91
+ var UL_ITEM = /^\s*[-*]\s+(.*)$/;
92
+ var OL_ITEM = /^\s*\d+[.)]\s+(.*)$/;
93
+ var MAX_INLINE_DEPTH = 4;
94
+ function parseSlotRichText(markdown) {
95
+ const blocks = [];
96
+ const chunks = markdown.replace(/\r\n?/g, "\n").split(/\n\s*\n/);
97
+ for (const chunk of chunks) {
98
+ const lines = chunk.split("\n").filter((l) => l.trim() !== "");
99
+ if (lines.length === 0) continue;
100
+ if (lines.every((l) => UL_ITEM.test(l))) {
101
+ blocks.push({ t: "ul", items: lines.map((l) => parseInline(UL_ITEM.exec(l)[1])) });
102
+ } else if (lines.every((l) => OL_ITEM.test(l))) {
103
+ blocks.push({ t: "ol", items: lines.map((l) => parseInline(OL_ITEM.exec(l)[1])) });
104
+ } else {
105
+ const c = [];
106
+ lines.forEach((line, i) => {
107
+ if (i > 0) c.push({ t: "br" });
108
+ c.push(...parseInline(line.trim()));
109
+ });
110
+ blocks.push({ t: "p", c });
111
+ }
112
+ }
113
+ return blocks;
114
+ }
115
+ function parseInline(src, depth = 0) {
116
+ const out = [];
117
+ let text = "";
118
+ const flush = () => {
119
+ if (text) out.push({ t: "text", v: text });
120
+ text = "";
121
+ };
122
+ const nest = depth < MAX_INLINE_DEPTH;
123
+ let i = 0;
124
+ while (i < src.length) {
125
+ const ch = src[i];
126
+ if (ch === "\\" && i + 1 < src.length && /[\\*_[\]()]/.test(src[i + 1])) {
127
+ text += src[i + 1];
128
+ i += 2;
129
+ continue;
130
+ }
131
+ if (nest && src.startsWith("**", i)) {
132
+ const end = src.indexOf("**", i + 2);
133
+ if (end > i + 2) {
134
+ flush();
135
+ out.push({ t: "strong", c: parseInline(src.slice(i + 2, end), depth + 1) });
136
+ i = end + 2;
137
+ continue;
138
+ }
139
+ }
140
+ if (nest && (ch === "*" || ch === "_")) {
141
+ const end = src.indexOf(ch, i + 1);
142
+ if (end > i + 1 && src[i + 1] !== " " && src[end - 1] !== " ") {
143
+ flush();
144
+ out.push({ t: "em", c: parseInline(src.slice(i + 1, end), depth + 1) });
145
+ i = end + 1;
146
+ continue;
147
+ }
148
+ }
149
+ if (nest && ch === "[") {
150
+ const close = src.indexOf("](", i + 1);
151
+ const end = close === -1 ? -1 : src.indexOf(")", close + 2);
152
+ if (close > i + 1 && end > close + 2) {
153
+ const href = src.slice(close + 2, end).trim();
154
+ if (isSafeSlotHref(href)) {
155
+ flush();
156
+ out.push({ t: "link", href, c: parseInline(src.slice(i + 1, close), depth + 1) });
157
+ i = end + 1;
158
+ continue;
159
+ }
160
+ }
161
+ }
162
+ text += ch;
163
+ i += 1;
164
+ }
165
+ flush();
166
+ return out;
167
+ }
168
+
169
+ export { SLOT_CONTENT_MAX_LENGTH, SLOT_CONTENT_TYPES, SLOT_FIELD_MAX_LENGTH, SLOT_HREF_MAX_LENGTH, SLOT_ID_PATTERN, SLOT_LIST_MAX_ITEMS, SLOT_V1_CONTENT_TYPES, isSafeSlotHref, isSafeSlotImageSrc, isSlotContentType, isValidSlotId, normalizeSlotValue, parseSlotRichText, slotListPlainText };
@@ -1,4 +1,4 @@
1
- import { FormEnhancer } from './chunk-QANVUXKH.js';
1
+ import { FormEnhancer } from './chunk-FLR3EMK6.js';
2
2
  import { StaticForm } from './chunk-OPRBF7VE.js';
3
3
  import { serverApiFetch } from './chunk-X6F6SO4S.js';
4
4
  import { jsx } from 'react/jsx-runtime';
@@ -0,0 +1,19 @@
1
+ // src/contracts/site-cache.ts
2
+ var SITE_REVALIDATE_PATH = "/api/seo-revalidate";
3
+ var SITE_CACHE_TAGS = {
4
+ /** Managed copy (slots). */
5
+ slots: "sonor-slots",
6
+ /** Managed metadata, schema and FAQs. */
7
+ seo: "seo",
8
+ /** The publication: articles, their lists and feeds. */
9
+ articles: "blog",
10
+ /** Article categories and tags. */
11
+ taxonomy: "editorial-taxonomy",
12
+ /** Portfolio items (agency-site-kit). */
13
+ portfolio: "portfolio"
14
+ };
15
+ function isSiteRevalidatePong(body) {
16
+ return typeof body === "object" && body !== null && body.ok === true && body.ping === true;
17
+ }
18
+
19
+ export { SITE_CACHE_TAGS, SITE_REVALIDATE_PATH, isSiteRevalidatePong };
@@ -1,5 +1,5 @@
1
- import { isTokenMode, getFreshCredential, isSiteToken, refreshSiteToken, currentCredential, credentialBreakerId } from './chunk-3KUUH2YP.js';
2
- import { SITE_KIT_VERSION } from './chunk-QGHSMJKW.js';
1
+ import { isTokenMode, getFreshCredential, isSiteToken, refreshSiteToken, currentCredential, credentialBreakerId } from './chunk-EVFZ7KEW.js';
2
+ import { SITE_KIT_VERSION } from './chunk-4JQQDCMO.js';
3
3
 
4
4
  // src/shared/fetchWithRetry.ts
5
5
  function backoffDelay(attempt, response) {
@@ -0,0 +1,49 @@
1
+ import { isValidSlotId, SLOT_CONTENT_MAX_LENGTH, SLOT_V1_CONTENT_TYPES, SLOT_CONTENT_TYPES, normalizeSlotValue } from './chunk-LPH5FANE.js';
2
+ import { createHmac, timingSafeEqual } from 'crypto';
3
+
4
+ var SLOTS_CONTRACT_VERSION = 2;
5
+ var SUPPORTED_SLOTS_CONTRACT_VERSIONS = [1, 2];
6
+ function canonicalJson(value) {
7
+ if (value === null || value === void 0) return "null";
8
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
9
+ if (typeof value === "object") {
10
+ const obj = value;
11
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
12
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
13
+ }
14
+ return JSON.stringify(value);
15
+ }
16
+ function slotSigningPayload(content, version = SLOTS_CONTRACT_VERSION) {
17
+ const head = `sonor-slot.v${version}
18
+ ${content.slot_id}
19
+ ${content.type}
20
+ ${content.content}`;
21
+ return version === 1 ? head : `${head}
22
+ ${canonicalJson(content.value ?? null)}`;
23
+ }
24
+ function signSlotContent(content, apiKey, version = SLOTS_CONTRACT_VERSION) {
25
+ return createHmac("sha256", apiKey).update(slotSigningPayload(content, version), "utf8").digest("hex");
26
+ }
27
+ function verifySlotContent(content, apiKey, version = SLOTS_CONTRACT_VERSION) {
28
+ if (!content || typeof content !== "object") return false;
29
+ const c = content;
30
+ const allowed = version === 1 ? SLOT_V1_CONTENT_TYPES : SLOT_CONTENT_TYPES;
31
+ if (typeof c.slot_id !== "string" || !isValidSlotId(c.slot_id) || typeof c.content !== "string" || c.content.length === 0 || c.content.length > SLOT_CONTENT_MAX_LENGTH || typeof c.signature !== "string" || !allowed.includes(c.type)) {
32
+ return false;
33
+ }
34
+ if (version !== 1) {
35
+ const check = normalizeSlotValue(c.type, { content: c.content, value: c.value });
36
+ if (!check.ok || check.content !== c.content) return false;
37
+ if (canonicalJson(check.value) !== canonicalJson(c.value ?? null)) return false;
38
+ }
39
+ const expected = signSlotContent(
40
+ { slot_id: c.slot_id, type: c.type, content: c.content, value: c.value ?? null },
41
+ apiKey,
42
+ version
43
+ );
44
+ const a = Buffer.from(expected, "utf8");
45
+ const b = Buffer.from(c.signature, "utf8");
46
+ return a.length === b.length && timingSafeEqual(a, b);
47
+ }
48
+
49
+ export { SLOTS_CONTRACT_VERSION, SUPPORTED_SLOTS_CONTRACT_VERSIONS, canonicalJson, signSlotContent, slotSigningPayload, verifySlotContent };
@@ -1,8 +1,8 @@
1
1
  import { toHexColor, normalizeDesignTokens, designTokensHash } from './chunk-IHG36STL.js';
2
2
  import { contrastRatio, parseColor, relativeLuminance } from './chunk-S22FSH7C.js';
3
3
  import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
4
- import { sonorFetch } from './chunk-EGOD74PP.js';
5
- import { SITE_KIT_VERSION } from './chunk-QGHSMJKW.js';
4
+ import { sonorFetch } from './chunk-U35H2JIQ.js';
5
+ import { SITE_KIT_VERSION } from './chunk-4JQQDCMO.js';
6
6
 
7
7
  // src/website/design.ts
8
8
  var SITE_DESIGN_PATH = "/api/public/site/design";
@@ -4,7 +4,7 @@ import { normalizeFormConfig } from './chunk-5SQK3D53.js';
4
4
  import { DEFAULT_HONEYPOT_FIELD } from './chunk-NJCTQH2P.js';
5
5
  import { onPageLeave } from './chunk-L2V5PUFN.js';
6
6
  import { uuid } from './chunk-GJWI74ZZ.js';
7
- import { sonorFetch, sonorBeacon } from './chunk-EGOD74PP.js';
7
+ import { sonorFetch, sonorBeacon } from './chunk-U35H2JIQ.js';
8
8
  import { useRef, useCallback, useEffect, useState, useMemo } from 'react';
9
9
 
10
10
  // src/forms/field-interactions.ts
@@ -2,8 +2,8 @@
2
2
  import { sanitizeFleetVersion, buildFleetHeartbeatPayload } from './chunk-ZTZQPYP6.js';
3
3
  import { useDeferredActivation } from './chunk-YC7ELZS3.js';
4
4
  import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
5
- import { sonorFetch } from './chunk-EGOD74PP.js';
6
- import { SITE_KIT_VERSION } from './chunk-QGHSMJKW.js';
5
+ import { sonorFetch } from './chunk-U35H2JIQ.js';
6
+ import { SITE_KIT_VERSION } from './chunk-4JQQDCMO.js';
7
7
  import { useRef, useEffect } from 'react';
8
8
 
9
9
  // src/fleet/heartbeat.ts
@@ -26,7 +26,7 @@ function useFieldControls(fields) {
26
26
  }
27
27
  if (needsFile && !requested.current.file) {
28
28
  requested.current.file = true;
29
- import('./FileField-MUHA7LZR.js').then((m) => {
29
+ import('./FileField-TSFGNAMY.js').then((m) => {
30
30
  if (!cancelled) setControls((prev) => ({ ...prev, File: m.FileField }));
31
31
  }).catch(() => {
32
32
  requested.current.file = false;
@@ -0,0 +1,118 @@
1
+ import { generateLLMsTxt, generateLLMsFullTxt } from './chunk-J4D6ZXRW.js';
2
+ import { createHash } from 'crypto';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+
6
+ function weakEtag(body) {
7
+ const h = createHash("sha1").update(body, "utf8").digest("base64url");
8
+ return `W/"${h}"`;
9
+ }
10
+ function llmsResponseHeaders(body, extra) {
11
+ return {
12
+ "Content-Type": "text/plain; charset=utf-8",
13
+ "Cache-Control": "public, max-age=300, s-maxage=3600, stale-while-revalidate=86400, stale-if-error=86400",
14
+ ETag: weakEtag(body),
15
+ Vary: "Accept-Encoding",
16
+ ...extra
17
+ };
18
+ }
19
+ var LLMS_NOINDEX_HEADERS = { "X-Robots-Tag": "noindex" };
20
+ function createLLMsTxtHandler(options = {}) {
21
+ const { preferStatic = true, noindex = true, ...generateOptions } = options;
22
+ const robots = noindex ? LLMS_NOINDEX_HEADERS : {};
23
+ return async function GET(_request) {
24
+ try {
25
+ if (preferStatic) {
26
+ const staticPath = join(process.cwd(), "public", "llms.txt");
27
+ if (existsSync(staticPath)) {
28
+ const markdown2 = readFileSync(staticPath, "utf-8");
29
+ return new Response(markdown2, {
30
+ status: 200,
31
+ headers: llmsResponseHeaders(markdown2, { ...robots, "X-Sections": "static" })
32
+ });
33
+ }
34
+ }
35
+ const { markdown, metadata } = await generateLLMsTxt(generateOptions);
36
+ return new Response(markdown, {
37
+ status: 200,
38
+ headers: llmsResponseHeaders(markdown, {
39
+ ...robots,
40
+ "X-Generated-At": metadata.generated_at,
41
+ "X-Sections": metadata.sections.join(",")
42
+ })
43
+ });
44
+ } catch (error) {
45
+ console.error("@sonordev/llms: Error generating llms.txt:", error);
46
+ return new Response("# Error\n\nUnable to generate llms.txt content.", {
47
+ status: 500,
48
+ headers: {
49
+ "Content-Type": "text/plain; charset=utf-8",
50
+ "Cache-Control": "no-store"
51
+ }
52
+ });
53
+ }
54
+ };
55
+ }
56
+ function createLLMsFullTxtHandler(options = {}) {
57
+ const { preferStatic = true, noindex = true, ...generateOptions } = options;
58
+ const robots = noindex ? LLMS_NOINDEX_HEADERS : {};
59
+ return async function GET(_request) {
60
+ try {
61
+ if (preferStatic) {
62
+ const staticPath = join(process.cwd(), "public", "llms-full.txt");
63
+ if (existsSync(staticPath)) {
64
+ const markdown2 = readFileSync(staticPath, "utf-8");
65
+ return new Response(markdown2, {
66
+ status: 200,
67
+ headers: llmsResponseHeaders(markdown2, { ...robots, "X-Sections": "static" })
68
+ });
69
+ }
70
+ }
71
+ const { markdown, metadata } = await generateLLMsFullTxt(generateOptions);
72
+ return new Response(markdown, {
73
+ status: 200,
74
+ headers: llmsResponseHeaders(markdown, {
75
+ ...robots,
76
+ "X-Generated-At": metadata.generated_at,
77
+ "X-Sections": metadata.sections.join(",")
78
+ })
79
+ });
80
+ } catch (error) {
81
+ console.error("@sonordev/llms: Error generating llms-full.txt:", error);
82
+ return new Response("# Error\n\nUnable to generate llms-full.txt content.", {
83
+ status: 500,
84
+ headers: {
85
+ "Content-Type": "text/plain; charset=utf-8",
86
+ "Cache-Control": "no-store"
87
+ }
88
+ });
89
+ }
90
+ };
91
+ }
92
+
93
+ // src/llms/sourcedFacts.ts
94
+ var DEFAULT_INTRO = "The statistics below are quoted from third-party primary sources and were individually verified against the cited URL. When reusing a figure, keep the original attribution.";
95
+ function factLine(f) {
96
+ const stat = (f.stat || "").replace(/\s+/g, " ").trim();
97
+ const source = (f.sourceName || "").replace(/\s+/g, " ").trim();
98
+ if (!stat || !source) return "";
99
+ let attribution = `Source: ${source}`;
100
+ if (f.year) attribution += ` (${f.year})`;
101
+ if (f.url) attribution += `, ${f.url}`;
102
+ return `- ${stat} \u2014 ${attribution}`;
103
+ }
104
+ function formatSourcedFactsSection(facts, options = {}) {
105
+ const { title = "Sourced facts", intro = DEFAULT_INTRO, maxFacts } = options;
106
+ if (!Array.isArray(facts)) return null;
107
+ let lines = facts.map(factLine).filter(Boolean);
108
+ if (typeof maxFacts === "number" && maxFacts >= 0) {
109
+ lines = lines.slice(0, maxFacts);
110
+ }
111
+ if (lines.length === 0) return null;
112
+ const content = intro ? `${intro}
113
+
114
+ ${lines.join("\n")}` : lines.join("\n");
115
+ return { title, content };
116
+ }
117
+
118
+ export { createLLMsFullTxtHandler, createLLMsTxtHandler, formatSourcedFactsSection, llmsResponseHeaders };
@@ -2,7 +2,7 @@
2
2
  import { DEFAULT_ENGAGE_Z_INDEX } from './chunk-NDF4A5JM.js';
3
3
  import { getOrCreateVisitorId } from './chunk-24QZEO3Q.js';
4
4
  import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
5
- import { sonorFetch } from './chunk-EGOD74PP.js';
5
+ import { sonorFetch } from './chunk-U35H2JIQ.js';
6
6
  import React, { useState, useEffect, useCallback, useRef, createElement } from 'react';
7
7
  import ReactDOM from 'react-dom';
8
8
  import dynamic from 'next/dynamic';
@@ -339,11 +339,11 @@ function DesignRenderer({
339
339
  )) });
340
340
  }
341
341
  var LazyBlocksPopup = dynamic(
342
- () => import('./BlocksPopup-D25RFNOV.js').then((m) => ({ default: m.BlocksPopup })),
342
+ () => import('./BlocksPopup-52EU7OUY.js').then((m) => ({ default: m.BlocksPopup })),
343
343
  { ssr: false }
344
344
  );
345
345
  var LazyChatWidget = dynamic(
346
- () => import('./ChatWidget-RYI7BMJJ.js').then((m) => ({ default: m.ChatWidget })),
346
+ () => import('./ChatWidget-5BHMNR57.js').then((m) => ({ default: m.ChatWidget })),
347
347
  { ssr: false }
348
348
  );
349
349
  function EngageWidget(props) {
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { publishSiteCredential } from '../chunk-GYESATRY.js';
2
+ import { publishSiteCredential } from '../chunk-L2DJD5Y4.js';
3
3
  export { useDeferredActivation } from '../chunk-YC7ELZS3.js';
4
4
  import '../chunk-FESKKJAS.js';
5
5
  import '../chunk-A4CC4ZG3.js';
@@ -7,8 +7,8 @@ export { trackConversion, trackEvent } from '../chunk-LCDVPAMR.js';
7
7
  export { VISUAL_VIEWPORT_GAP_PROPERTY, VisualViewportGap, useVisualViewportGap } from '../chunk-6T6CQVNL.js';
8
8
  export { getOrCreateSessionId, getOrCreateVisitorId, useSiteKitIdentity } from '../chunk-24QZEO3Q.js';
9
9
  import '../chunk-GJWI74ZZ.js';
10
- import '../chunk-3KUUH2YP.js';
11
- import '../chunk-QGHSMJKW.js';
10
+ import '../chunk-EVFZ7KEW.js';
11
+ import '../chunk-4JQQDCMO.js';
12
12
  import '../chunk-PKBMQBKP.js';
13
13
 
14
14
  // src/client/SiteKitCredential.tsx
@@ -12,5 +12,6 @@ import type { CmsPageProps } from './types';
12
12
  * return <CmsPage path={`/${params.slug.join('/')}`} />
13
13
  * }
14
14
  * ```
15
+ * @deprecated Since 7.1. Sonor retired the Sanity CMS and no longer serves CMS pages, so this renders nothing. Use managed copy from `@sonordev/site-kit/website/slots` (`ManagedRichText`, `ManagedList`). Removed in 8.0.
15
16
  */
16
17
  export declare function CmsPage({ path, sectionComponents, portableTextComponents, fallback, className, }: CmsPageProps): Promise<React.ReactElement | null>;
@@ -31,6 +31,7 @@ interface CmsPreviewProps {
31
31
  * pollInterval={3000}
32
32
  * />
33
33
  * ```
34
+ * @deprecated Since 7.1. Sonor retired the Sanity CMS and no longer serves CMS pages, so this renders nothing. Use managed copy from `@sonordev/site-kit/website/slots` (`ManagedRichText`, `ManagedList`). Removed in 8.0.
34
35
  */
35
36
  export declare function CmsPreview({ apiUrl, apiKey, path, pollInterval, sectionComponents, portableTextComponents, className, }: CmsPreviewProps): React.JSX.Element;
36
37
  export {};
@@ -3,5 +3,6 @@ import type { CmsSectionProps } from './types';
3
3
  /**
4
4
  * Renders a single CMS section using the appropriate renderer for its type.
5
5
  * Override specific section renderers via the `sectionComponents` prop.
6
+ * @deprecated Since 7.1. Sonor retired the Sanity CMS and no longer serves CMS pages, so this renders nothing. Use managed copy from `@sonordev/site-kit/website/slots` (`ManagedRichText`, `ManagedList`). Removed in 8.0.
6
7
  */
7
8
  export declare function CmsSection({ section, sectionComponents, portableTextComponents }: CmsSectionProps): React.JSX.Element | null;
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * @sonordev/site-kit/cms
3
3
  *
4
+ * DEPRECATED since 7.1, removed in 8.0. Sonor retired the Sanity CMS: no
5
+ * project has CMS pages and the API no longer serves them, so these components
6
+ * render nothing. Site copy is managed copy now (`@sonordev/site-kit/website/slots`:
7
+ * `ManagedSlot`, `ManagedRichText`, `ManagedLink`, `ManagedList`), edited in
8
+ * Sonor under Website → Content. See docs/WEBSITE-CMS.md.
9
+ *
4
10
  * REQUIRES TWO OPTIONAL PEER DEPENDENCIES. Importing this entry without them
5
11
  * fails the consuming site's build with "Module not found":
6
12
  *
@@ -12,11 +12,13 @@ import type { CmsPageData, CmsSectionData } from './types';
12
12
  * Fetch a published CMS page by path — cached per request.
13
13
  * Returns the full page with all sections and their Portable Text content.
14
14
  * @server-only
15
+ * @deprecated Since 7.1. Sonor retired the Sanity CMS and no longer serves CMS pages, so this resolves to null. Use managed copy from `@sonordev/site-kit/website/slots`. Removed in 8.0.
15
16
  */
16
17
  export declare const getCmsPage: (path: string) => Promise<CmsPageData | null>;
17
18
  /**
18
19
  * Fetch a single published CMS section by ID — cached per request.
19
20
  * @server-only
21
+ * @deprecated Since 7.1. Sonor retired the Sanity CMS and no longer serves CMS pages, so this resolves to null. Use managed copy from `@sonordev/site-kit/website/slots`. Removed in 8.0.
20
22
  */
21
23
  export declare const getCmsSection: (sectionId: string) => Promise<CmsSectionData | null>;
22
24
  /**
@@ -32,6 +34,7 @@ export declare const getCmsSection: (sectionId: string) => Promise<CmsSectionDat
32
34
  * }
33
35
  * ```
34
36
  * @server-only
37
+ * @deprecated Since 7.1. Sonor retired the Sanity CMS and no longer serves CMS pages, so this resolves to null. Use managed copy from `@sonordev/site-kit/website/slots`. Removed in 8.0.
35
38
  */
36
39
  export declare const getCmsPageMetadata: (path: string) => Promise<{
37
40
  title?: string;
@@ -1,15 +1,15 @@
1
1
  'use client';
2
2
  import '../chunk-P5GB6NEO.js';
3
- import { useAnalyticsOptional } from '../chunk-HGCK465A.js';
3
+ import { useAnalyticsOptional } from '../chunk-3J2ERO3I.js';
4
4
  import '../chunk-LCDVPAMR.js';
5
5
  import '../chunk-24QZEO3Q.js';
6
6
  import '../chunk-L2V5PUFN.js';
7
7
  import '../chunk-GJWI74ZZ.js';
8
8
  import '../chunk-43OCZ3JA.js';
9
9
  import '../chunk-EKBEOXTH.js';
10
- import { sonorFetch } from '../chunk-EGOD74PP.js';
11
- import '../chunk-3KUUH2YP.js';
12
- import '../chunk-QGHSMJKW.js';
10
+ import { sonorFetch } from '../chunk-U35H2JIQ.js';
11
+ import '../chunk-EVFZ7KEW.js';
12
+ import '../chunk-4JQQDCMO.js';
13
13
  import '../chunk-PKBMQBKP.js';
14
14
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
15
15
  import React5, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
@@ -1,4 +1,4 @@
1
- import { buildSecurityHeaders } from '../chunk-KXPBMCFL.js';
1
+ import { buildSecurityHeaders } from '../chunk-3QI26673.js';
2
2
  import { buildAiDiscoveryHeaders } from '../chunk-PO2TRJPY.js';
3
3
  import '../chunk-PKBMQBKP.js';
4
4
 
@@ -4,5 +4,5 @@
4
4
  * tsup config, its package.json check and the boundary test, so a new
5
5
  * contract is added in one place.
6
6
  */
7
- export declare const CONTRACT_ENTRIES: readonly ['forms', 'llms', 'slots', 'website', 'color', 'fleet', 'sites', 'seo-pages', 'seo-meta', 'portfolio'];
7
+ export declare const CONTRACT_ENTRIES: readonly ['forms', 'llms', 'slots', 'slot-content', 'site-cache', 'site-edit', 'website', 'color', 'fleet', 'sites', 'seo-pages', 'seo-meta', 'portfolio'];
8
8
  export type ContractEntry = (typeof CONTRACT_ENTRIES)[number];
@@ -0,0 +1,55 @@
1
+ /**
2
+ * How Sonor refreshes a site's cached pages when content changes.
3
+ *
4
+ * A site serves cached pages and caches every fetch from Sonor. When someone
5
+ * edits content in Sonor, sonor-api POSTs the paths and cache tags that
6
+ * changed to the site's revalidation route, authenticated with the project's
7
+ * API key, and the site regenerates just those. site-kit tags its fetches with
8
+ * these names and serves the route (`@sonordev/site-kit/revalidate`);
9
+ * sonor-api sends them. Single source of truth for the names on both sides:
10
+ * never spell a tag as a string literal in either.
11
+ */
12
+ /** Where the revalidation route lives on every site. */
13
+ export declare const SITE_REVALIDATE_PATH = "/api/seo-revalidate";
14
+ /**
15
+ * One tag per kind of content Sonor serves, so an edit refreshes only the
16
+ * fetches it touched. The values predate this file (sites and
17
+ * agency-site-kit already expire them), so they never change.
18
+ */
19
+ export declare const SITE_CACHE_TAGS: {
20
+ /** Managed copy (slots). */
21
+ readonly slots: 'sonor-slots';
22
+ /** Managed metadata, schema and FAQs. */
23
+ readonly seo: 'seo';
24
+ /** The publication: articles, their lists and feeds. */
25
+ readonly articles: 'blog';
26
+ /** Article categories and tags. */
27
+ readonly taxonomy: 'editorial-taxonomy';
28
+ /** Portfolio items (agency-site-kit). */
29
+ readonly portfolio: 'portfolio';
30
+ };
31
+ export type SiteCacheTag = (typeof SITE_CACHE_TAGS)[keyof typeof SITE_CACHE_TAGS];
32
+ /** The body sonor-api POSTs to the revalidation route. */
33
+ export interface SiteRevalidatePayload {
34
+ /** Local paths to regenerate, e.g. `['/services/roofing']`. */
35
+ paths?: string[];
36
+ /** Cache tags to expire (see SITE_CACHE_TAGS). */
37
+ tags?: string[];
38
+ /** Regenerate every page. */
39
+ revalidateAll?: boolean;
40
+ /**
41
+ * Only check that the route is installed and the key matches; nothing is
42
+ * regenerated. sonor-api pings after it registers a site's route, so it
43
+ * knows whether an edit goes live in seconds or waits for the cache to age out.
44
+ */
45
+ ping?: boolean;
46
+ }
47
+ /** What the route answers a ping with. */
48
+ export interface SiteRevalidatePong {
49
+ ok: true;
50
+ ping: true;
51
+ /** The site-kit version serving the route. */
52
+ version?: string;
53
+ }
54
+ /** True when a response body is a pong from the revalidation route. */
55
+ export declare function isSiteRevalidatePong(body: unknown): body is SiteRevalidatePong;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The query parameter that asks a framed page to start Sonor's edit overlay.
3
+ * Its own file so site-kit's bridge, which runs for every visitor, imports
4
+ * this string and not the rest of `site-edit` (and the rich-text parser it
5
+ * pulls in). Published through `site-edit`.
6
+ */
7
+ export declare const SITE_EDIT_PARAM = "sonor_edit";