@sonordev/site-kit 7.1.2 → 7.2.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 (167) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +51 -0
  3. package/README.md +9 -8
  4. package/agent-manifest.json +12 -4
  5. package/dist/{AnalyticsProvider-ZB33W6MC.js → AnalyticsProvider-SLFFBRPE.js} +4 -4
  6. package/dist/{ArticleViewTracker-GMHYLLLZ.js → ArticleViewTracker-TM5AMGN2.js} +3 -3
  7. package/dist/{BlocksPopup-EA6LYL25.js → BlocksPopup-YJNJIVPO.js} +5 -5
  8. package/dist/ChatWidget-XKOSH66S.js +17 -0
  9. package/dist/{FileField-QKA3JB2Q.js → FileField-CI4UL5SH.js} +3 -3
  10. package/dist/{FormSpotlight-AQTIA5TP.js → FormSpotlight-F2AAEGBN.js} +20 -6
  11. package/dist/{FormStage-FDTLTP3B.js → FormStage-BI5R5CNC.js} +27 -7
  12. package/dist/ManagedForm-G6COGHXL.js +16 -0
  13. package/dist/{ManagedNewsletterForm-J6QSAGGP.js → ManagedNewsletterForm-BO3WCGXX.js} +7 -5
  14. package/dist/{SignalCore-JZNXOLNT.js → SignalCore-ZA6WHKBR.js} +3 -3
  15. package/dist/SiteChat-YJADDTKG.js +5 -0
  16. package/dist/{SiteDesignReporter-W4EJUSTE.js → SiteDesignReporter-5YAW36AQ.js} +5 -5
  17. package/dist/SitePopups-ST5PMKZE.js +10 -0
  18. package/dist/SitemapSync-RJTEBINX.js +8 -0
  19. package/dist/_client/booking-widget.js +5 -5
  20. package/dist/affiliates/index.js +3 -3
  21. package/dist/analytics/index.js +4 -4
  22. package/dist/analytics/send-gate.d.ts +1 -1
  23. package/dist/articles/index.js +2 -2
  24. package/dist/articles/server-ui.js +3 -2
  25. package/dist/articles/server.js +2 -1
  26. package/dist/{engage → chat}/ChatWidget.d.ts +7 -6
  27. package/dist/{engage → chat}/EchoUiActions.d.ts +1 -1
  28. package/dist/chat/SiteChat.d.ts +25 -0
  29. package/dist/{engage → chat}/brand-color.d.ts +1 -1
  30. package/dist/{engage → chat}/chat-messages.d.ts +1 -1
  31. package/dist/{engage → chat}/echo-config.d.ts +1 -1
  32. package/dist/chat/index.d.ts +10 -6
  33. package/dist/chat/index.js +14 -8
  34. package/dist/{engage → chat}/launcher-placement.d.ts +1 -7
  35. package/dist/{engage → chat}/socket-loader.d.ts +2 -2
  36. package/dist/chat/types.d.ts +136 -0
  37. package/dist/chunk-27FISYK4.js +4 -0
  38. package/dist/{chunk-LUXVWITO.js → chunk-4NTBQNHA.js} +247 -78
  39. package/dist/{chunk-V3PUVBME.js → chunk-5V2V5C3Y.js} +2 -4
  40. package/dist/{chunk-Q4W23JYM.js → chunk-5YCEMV2L.js} +1 -1
  41. package/dist/{chunk-RHNWA34F.js → chunk-64DKGVEI.js} +41 -7
  42. package/dist/{chunk-GLHQ3LRO.js → chunk-6U3VLV2C.js} +1 -1
  43. package/dist/{chunk-WWSHFRDL.js → chunk-BC4Y3S3D.js} +1 -1
  44. package/dist/{chunk-JSZN6LDZ.js → chunk-BF7TZYC3.js} +38 -31
  45. package/dist/{chunk-M3YEPKP7.js → chunk-BKY2ZNM7.js} +1 -1
  46. package/dist/{chunk-AQSNPZY4.js → chunk-C2FZBUSS.js} +80 -1
  47. package/dist/{chunk-NK4CSJNP.js → chunk-CF5QNFYU.js} +2 -2
  48. package/dist/{chunk-D5EC5KVO.js → chunk-D5DCBD4Y.js} +2 -2
  49. package/dist/{chunk-3SDOQUUF.js → chunk-D6ZC7XKZ.js} +2 -2
  50. package/dist/chunk-EJVQJCG5.js +370 -0
  51. package/dist/{chunk-AH5Q262S.js → chunk-F24FNCTK.js} +1 -1
  52. package/dist/{chunk-V6FDQLR7.js → chunk-FR6BYFPC.js} +53 -21
  53. package/dist/{chunk-UXOGCKL6.js → chunk-GEXT7BTE.js} +1 -1
  54. package/dist/{chunk-JTU2GOM4.js → chunk-GGORNBSC.js} +18 -6
  55. package/dist/{chunk-ERXXITG4.js → chunk-HGFAEDG3.js} +1 -1
  56. package/dist/chunk-HODO5BX5.js +28 -0
  57. package/dist/chunk-HW7B43E5.js +14 -0
  58. package/dist/{chunk-CSTQDNGK.js → chunk-IAM3D3RL.js} +43 -24
  59. package/dist/{chunk-PGMVL4AB.js → chunk-JJ3DELUL.js} +2 -2
  60. package/dist/{chunk-ILU5I6RW.js → chunk-JMLK6MH7.js} +1 -1
  61. package/dist/{chunk-YEMPUQVU.js → chunk-JWTQS5F5.js} +2 -2
  62. package/dist/{chunk-VDF5DFWT.js → chunk-K4ZHAURD.js} +1 -1
  63. package/dist/chunk-NV72HZ4W.js +45 -0
  64. package/dist/{chunk-4Y5FWDPM.js → chunk-NZ4RXN3G.js} +55 -7
  65. package/dist/chunk-OWZ7ATLZ.js +187 -0
  66. package/dist/{chunk-TXBAOEKG.js → chunk-PKGN32AU.js} +5 -4
  67. package/dist/chunk-Q6E4OPGG.js +40 -0
  68. package/dist/{chunk-QEDHRBAT.js → chunk-QL6XNPSD.js} +2 -2
  69. package/dist/{chunk-JBE46QJZ.js → chunk-R46IQIGK.js} +1 -1
  70. package/dist/{chunk-6WIQCXOO.js → chunk-RPZ7OQGD.js} +1 -1
  71. package/dist/{chunk-PHYMNUJU.js → chunk-S54ECQGY.js} +1 -1
  72. package/dist/{chunk-VGH4MDTU.js → chunk-VHKEF5ZB.js} +1 -1
  73. package/dist/{chunk-HF57LG73.js → chunk-VO3JYRT5.js} +43 -29
  74. package/dist/chunk-VWWITFWM.js +300 -0
  75. package/dist/{chunk-CIG5UL2S.js → chunk-ZTLMPUO6.js} +3 -2
  76. package/dist/client/index.js +3 -3
  77. package/dist/commerce/index.js +4 -4
  78. package/dist/contracts/color.d.ts +1 -1
  79. package/dist/contracts/entries.d.ts +1 -1
  80. package/dist/contracts/error-page.d.ts +183 -0
  81. package/dist/contracts/llms.d.ts +1 -0
  82. package/dist/contracts/proposal-sitemap.d.ts +130 -0
  83. package/dist/contracts/schema-placeholders.d.ts +87 -0
  84. package/dist/engage/EngageWidget.d.ts +13 -12
  85. package/dist/engage/index.d.ts +10 -9
  86. package/dist/engage/index.js +31 -35
  87. package/dist/engage/types.d.ts +19 -244
  88. package/dist/fleet/FleetHeartbeat.d.ts +1 -1
  89. package/dist/fleet/index.js +4 -4
  90. package/dist/forms/FormEnhancer.d.ts +25 -12
  91. package/dist/forms/ServerForm.d.ts +11 -10
  92. package/dist/forms/StaticForm.d.ts +3 -1
  93. package/dist/forms/field-autocomplete.d.ts +28 -0
  94. package/dist/forms/form-dom-values.d.ts +42 -0
  95. package/dist/forms/index.js +11 -9
  96. package/dist/forms/server.js +6 -4
  97. package/dist/forms/static.js +3 -2
  98. package/dist/forms/submitForm.d.ts +8 -1
  99. package/dist/forms/types.d.ts +41 -2
  100. package/dist/forms/useForm.d.ts +21 -4
  101. package/dist/forms/webmcp.d.ts +38 -0
  102. package/dist/images/index.js +4 -4
  103. package/dist/index.d.ts +4 -1
  104. package/dist/index.js +1 -1
  105. package/dist/layout/SiteKitClientProviders.d.ts +3 -3
  106. package/dist/layout/SiteKitLayout.d.ts +4 -4
  107. package/dist/layout/client.d.ts +3 -3
  108. package/dist/layout/client.js +7 -7
  109. package/dist/layout/index.js +8 -8
  110. package/dist/layout/types.d.ts +5 -4
  111. package/dist/llms/contract.js +1 -1
  112. package/dist/llms/index.js +6 -6
  113. package/dist/maps/index.js +3 -3
  114. package/dist/mcp/index.js +2 -14
  115. package/dist/mcp/sonor.js +14 -11
  116. package/dist/revalidate/index.js +2 -2
  117. package/dist/seo/client.js +4 -4
  118. package/dist/seo/index.js +15 -8
  119. package/dist/seo/llms/contract.js +1 -1
  120. package/dist/seo/llms.js +6 -6
  121. package/dist/seo/register-sitemap-cli.js +1 -1
  122. package/dist/seo/sitemap.js +4 -4
  123. package/dist/server/index.js +2 -2
  124. package/dist/shared/dialog.d.ts +27 -0
  125. package/dist/shared/identity.d.ts +1 -1
  126. package/dist/shared/layers.d.ts +7 -0
  127. package/dist/shared/mid-form.d.ts +53 -0
  128. package/dist/shared/reporting-gate.d.ts +1 -1
  129. package/dist/shared/version.d.ts +1 -1
  130. package/dist/shared/visual-viewport-gap.d.ts +1 -1
  131. package/dist/signal/index.js +2 -2
  132. package/dist/signal/types.d.ts +1 -1
  133. package/dist/sitemap/index.js +4 -4
  134. package/dist/{socket-loader-R24ZSRSQ.js → socket-loader-CGIPEG74.js} +1 -1
  135. package/dist/sync/index.js +5 -5
  136. package/dist/types.d.ts +3 -1
  137. package/dist/{web-vitals.attribution-GD6LGLVF.js → web-vitals.attribution-N2PCCF4Q.js} +1 -1
  138. package/dist/website/BlocksPopup.d.ts +1 -1
  139. package/dist/website/PopupBlocks.d.ts +5 -2
  140. package/dist/website/SitePopups.d.ts +25 -0
  141. package/dist/website/images.js +4 -4
  142. package/dist/website/index.js +6 -6
  143. package/dist/website/popup-rules.d.ts +46 -0
  144. package/dist/website/popup-types.d.ts +113 -0
  145. package/dist/website/popups.d.ts +2 -6
  146. package/dist/website/popups.js +6 -14
  147. package/dist/{writeLLMsTxt-G3JBNBC5.js → writeLLMsTxt-OV24LQVL.js} +3 -3
  148. package/docs/MIGRATING-TO-7.md +41 -9
  149. package/docs.json +2 -1
  150. package/package.json +2 -2
  151. package/src/analytics/README.md +2 -2
  152. package/src/articles/README.md +4 -1
  153. package/src/{engage → chat}/README.md +63 -62
  154. package/src/forms/README.md +49 -3
  155. package/src/layout/README.md +8 -5
  156. package/src/seo/README.md +3 -1
  157. package/src/sync/README.md +10 -0
  158. package/src/website/README.md +133 -0
  159. package/dist/ChatWidget-UCDWMWCW.js +0 -15
  160. package/dist/EngageWidget-7ZYSK4GE.js +0 -11
  161. package/dist/ManagedForm-H7BVAH2W.js +0 -14
  162. package/dist/SitemapSync-XBANGKHZ.js +0 -8
  163. package/dist/chunk-NDF4A5JM.js +0 -37
  164. package/dist/chunk-VLARKWZU.js +0 -100
  165. package/dist/chunk-YCJT4JJG.js +0 -837
  166. package/dist/engage/DesignRenderer.d.ts +0 -57
  167. package/dist/engage/element-rules.d.ts +0 -38
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { detectTimezone, releaseSlotHold, fetchBookingTypeDetails, fetchBookingTypes, fetchAvailability, createSlotHold, createBooking, formatDuration, formatDate, formatTime, prepareMeeting } from './chunk-M3YEPKP7.js';
2
+ import { detectTimezone, releaseSlotHold, fetchBookingTypeDetails, fetchBookingTypes, fetchAvailability, createSlotHold, createBooking, formatDuration, formatDate, formatTime, prepareMeeting } from './chunk-BKY2ZNM7.js';
3
3
  import { useState, useRef, useMemo, useCallback, useEffect, useId } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
 
@@ -163,6 +163,12 @@ function toLocalDateStr(d) {
163
163
  const day = String(d.getDate()).padStart(2, "0");
164
164
  return `${y}-${m}-${day}`;
165
165
  }
166
+ function dayLabel(d) {
167
+ return d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" });
168
+ }
169
+ function slotLabel(slot, timezone) {
170
+ return `${formatTime(slot.start, timezone)}, ${formatDate(slot.start, timezone)}`;
171
+ }
166
172
  function ChevronLeft() {
167
173
  return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M12.5 15L7.5 10L12.5 5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
168
174
  }
@@ -706,8 +712,8 @@ function BookingWidget({
706
712
  if (!disabled && !busyRef.current) setSelectedDate(date);
707
713
  },
708
714
  disabled,
709
- "aria-label": date.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" }),
710
- "aria-pressed": sel || void 0,
715
+ "aria-label": dayLabel(date),
716
+ "aria-pressed": Boolean(sel),
711
717
  children: date.getDate()
712
718
  },
713
719
  date.toISOString()
@@ -965,13 +971,15 @@ function TimeButton({
965
971
  timezone,
966
972
  loading
967
973
  }) {
974
+ const time = formatTime(slot.start, timezone);
975
+ const label = slotLabel(slot, timezone);
968
976
  if (selected && !confirmed) {
969
977
  return /* @__PURE__ */ jsxs("div", { className: "bw-time-btn-wrap selected", children: [
970
- /* @__PURE__ */ jsx("span", { className: "bw-time-text", children: formatTime(slot.start, timezone) }),
971
- /* @__PURE__ */ jsx("button", { className: "bw-time-confirm", onClick: onConfirm, disabled: loading, children: loading ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : "Confirm" })
978
+ /* @__PURE__ */ jsx("button", { type: "button", className: "bw-time-text", "aria-label": label, "aria-pressed": "true", onClick, disabled: loading, children: time }),
979
+ /* @__PURE__ */ jsx("button", { type: "button", className: "bw-time-confirm", "aria-label": `Confirm ${label}`, onClick: onConfirm, disabled: loading, children: loading ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : "Confirm" })
972
980
  ] });
973
981
  }
974
- return /* @__PURE__ */ jsx("button", { className: `bw-time-btn${selected ? " selected" : ""}`, onClick, disabled: loading, children: formatTime(slot.start, timezone) });
982
+ return /* @__PURE__ */ jsx("button", { type: "button", className: `bw-time-btn${selected ? " selected" : ""}`, "aria-label": label, "aria-pressed": selected, onClick, disabled: loading, children: time });
975
983
  }
976
984
  var WIDGET_CSS = `
977
985
  /* \u2500\u2500 Base \u2500\u2500 */
@@ -1159,7 +1167,11 @@ var WIDGET_CSS = `
1159
1167
  .bw-time-btn-wrap .bw-time-text {
1160
1168
  flex: 1; padding: 10px 12px; font-size: 0.875rem; font-weight: 500; color: var(--sk-text-primary); text-align: center;
1161
1169
  background: var(--sk-primary-light);
1170
+ -webkit-appearance: none; appearance: none; border: none; border-radius: 0; margin: 0;
1171
+ font-family: inherit; line-height: inherit; letter-spacing: inherit; cursor: default;
1162
1172
  }
1173
+ /* The wrap clips its children, so focus rings go inside, in the text color. */
1174
+ .bw-time-btn-wrap button:focus-visible { outline-color: currentColor; outline-offset: -2px; }
1163
1175
  .bw-time-confirm {
1164
1176
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
1165
1177
  min-height: 44px; padding: 10px 16px; border: none;
@@ -1,5 +1,5 @@
1
1
  import { resolveSiteHost } from './chunk-FESKKJAS.js';
2
- import { configureSiteCredential, currentCredential } from './chunk-VDF5DFWT.js';
2
+ import { configureSiteCredential, currentCredential } from './chunk-K4ZHAURD.js';
3
3
 
4
4
  // src/shared/publishCredential.ts
5
5
  function publishSiteCredential({
@@ -0,0 +1,28 @@
1
+ // src/chat/echo-config.ts
2
+ var SONOR_PUBLIC_ECHO_DEFAULTS = {
3
+ signalUrl: "https://signal.sonor.io",
4
+ welcomeMessage: "Hi! I'm Echo, Sonor's AI assistant. I can answer questions about our platform, help you understand pricing, or get you started with a demo. What would you like to know?",
5
+ suggestedQuestions: [
6
+ "What does Sonor do?",
7
+ "How does Signal AI work?",
8
+ "What are the pricing plans?",
9
+ "Can I migrate from HubSpot?",
10
+ "How does the SEO module work?"
11
+ ],
12
+ brandColor: "#2563eb",
13
+ // blue-600 (~5.2:1 on white, AA); overridden by --sk-primary CSS var or API brand_primary
14
+ assistantName: "Echo"
15
+ };
16
+ function getEchoWidgetProps(config = SONOR_PUBLIC_ECHO_DEFAULTS) {
17
+ return {
18
+ signal_enabled: true,
19
+ signal_url: config.signalUrl || SONOR_PUBLIC_ECHO_DEFAULTS.signalUrl,
20
+ welcome_message: config.welcomeMessage || SONOR_PUBLIC_ECHO_DEFAULTS.welcomeMessage,
21
+ suggested_questions: config.suggestedQuestions || SONOR_PUBLIC_ECHO_DEFAULTS.suggestedQuestions,
22
+ brand_color: config.brandColor || SONOR_PUBLIC_ECHO_DEFAULTS.brandColor,
23
+ assistant_name: config.assistantName || SONOR_PUBLIC_ECHO_DEFAULTS.assistantName,
24
+ assistant_avatar: config.assistantAvatar
25
+ };
26
+ }
27
+
28
+ export { SONOR_PUBLIC_ECHO_DEFAULTS, getEchoWidgetProps };
@@ -0,0 +1,14 @@
1
+ // src/mcp/declarative.ts
2
+ function declarativeToolForm(options) {
3
+ const attributes = {
4
+ toolname: options.name,
5
+ tooldescription: options.description
6
+ };
7
+ if (options.autoSubmit) attributes.toolautosubmit = "";
8
+ return attributes;
9
+ }
10
+ function declarativeToolField(description) {
11
+ return { toolparamdescription: description };
12
+ }
13
+
14
+ export { declarativeToolField, declarativeToolForm };
@@ -1,9 +1,10 @@
1
1
  'use client';
2
- import { countStageViews, countStageQuestions, useFieldControls } from './chunk-D5EC5KVO.js';
2
+ import { countStageViews, countStageQuestions, useFieldControls } from './chunk-D5DCBD4Y.js';
3
3
  import { HoneypotField } from './chunk-DXO6SBMD.js';
4
- import { DelightStyles, FormMomentum, FormField } from './chunk-LUXVWITO.js';
5
- import { useFormLoadedAt, useFormTracking, submitForm, useRecaptchaWarmup, useForm } from './chunk-V6FDQLR7.js';
6
- import { isFieldVisible, validateField, isFieldComplete, isDisplayOnly } from './chunk-AQSNPZY4.js';
4
+ import { DelightStyles, FormMomentum, FormField } from './chunk-4NTBQNHA.js';
5
+ import { useFormLoadedAt, useFormTracking, submitForm, useRecaptchaWarmup, useForm } from './chunk-FR6BYFPC.js';
6
+ import { readAgentSubmit, formToolAttributes } from './chunk-Q6E4OPGG.js';
7
+ import { isFieldVisible, validateField, isFieldComplete, isDisplayOnly, unseenControlValues } from './chunk-C2FZBUSS.js';
7
8
  import { DEFAULT_FORM_SUCCESS_MESSAGE } from './chunk-5SQK3D53.js';
8
9
  import { DEFAULT_HONEYPOT_FIELD } from './chunk-NJCTQH2P.js';
9
10
  import { useState, useRef, useMemo, useEffect, useCallback } from 'react';
@@ -62,7 +63,7 @@ function FormClient({
62
63
  return;
63
64
  }
64
65
  let cancelled = false;
65
- const load = resolvedExperience === "stage" ? import('./FormStage-FDTLTP3B.js').then((m) => m.FormStage) : import('./FormSpotlight-AQTIA5TP.js').then((m) => m.FormSpotlight);
66
+ const load = resolvedExperience === "stage" ? import('./FormStage-BI5R5CNC.js').then((m) => m.FormStage) : import('./FormSpotlight-F2AAEGBN.js').then((m) => m.FormSpotlight);
66
67
  load.then((Component) => {
67
68
  if (!cancelled) setExperience(() => Component);
68
69
  }).catch((err) => {
@@ -130,15 +131,16 @@ function FormClient({
130
131
  setErrors(newErrors);
131
132
  return Object.keys(newErrors).length === 0;
132
133
  }, [currentFields, validateField2]);
133
- const validateAll = useCallback(() => {
134
+ const validateAllFor = useCallback((vals) => {
134
135
  const allFields = config.fields || [];
136
+ const visible = (field) => isFieldVisible(field, vals);
135
137
  const newErrors = {};
136
138
  for (const field of allFields) {
137
- const error = validateField2(field);
139
+ const error = validateField(field, vals, visible);
138
140
  if (error) newErrors[field.slug] = error;
139
141
  }
140
142
  setErrors(newErrors);
141
- if (Object.keys(newErrors).length === 0) return { ok: true, firstErrorStep: null };
143
+ if (Object.keys(newErrors).length === 0) return { ok: true, firstErrorStep: null, errors: newErrors };
142
144
  let firstErrorStep = null;
143
145
  if (config.is_multi_step && config.steps) {
144
146
  const ordered = [...config.steps].sort((a, b) => a.step_number - b.step_number);
@@ -149,16 +151,16 @@ function FormClient({
149
151
  }
150
152
  }
151
153
  }
152
- return { ok: false, firstErrorStep };
153
- }, [config, validateField2]);
154
- const buildVisibleValues = useCallback(() => {
154
+ return { ok: false, firstErrorStep, errors: newErrors };
155
+ }, [config]);
156
+ const buildVisibleValues = useCallback((vals) => {
155
157
  const out = {};
156
158
  for (const field of config.fields || []) {
157
- if (!isFieldVisible2(field)) continue;
158
- if (field.slug in values) out[field.slug] = values[field.slug];
159
+ if (!isFieldVisible(field, vals)) continue;
160
+ if (field.slug in vals) out[field.slug] = vals[field.slug];
159
161
  }
160
162
  return out;
161
- }, [config, values, isFieldVisible2]);
163
+ }, [config]);
162
164
  const setFieldValue = useCallback((key, value) => {
163
165
  setTouched((prev) => prev[key] ? prev : { ...prev, [key]: true });
164
166
  setValues((prev) => ({ ...prev, [key]: value }));
@@ -197,15 +199,19 @@ function FormClient({
197
199
  trackStepChange(targetStep);
198
200
  }
199
201
  }, [step, config.total_steps, trackStepChange]);
200
- const submit = useCallback(async () => {
201
- if (isSubmittingRef.current) return;
202
- const { ok, firstErrorStep } = validateAll();
202
+ const submit = useCallback(async (options) => {
203
+ if (isSubmittingRef.current) return { status: "busy" };
204
+ const domFills = options && typeof options === "object" && options.domFills && typeof options.domFills === "object" ? options.domFills : null;
205
+ const agentInvoked = !!options && typeof options === "object" && options.agentInvoked === true;
206
+ const vals = domFills && Object.keys(domFills).length > 0 ? { ...values, ...domFills } : values;
207
+ if (vals !== values) setValues(vals);
208
+ const { ok, firstErrorStep, errors: invalid } = validateAllFor(vals);
203
209
  if (!ok) {
204
210
  if (firstErrorStep != null) {
205
211
  setStepDirection("back");
206
212
  setStep(firstErrorStep);
207
213
  }
208
- return;
214
+ return { status: "invalid", errors: invalid };
209
215
  }
210
216
  isSubmittingRef.current = true;
211
217
  setIsSubmitting(true);
@@ -213,9 +219,10 @@ function FormClient({
213
219
  try {
214
220
  const data = await submitForm({
215
221
  config,
216
- values: buildVisibleValues(),
222
+ values: buildVisibleValues(vals),
217
223
  honeypotValue: config.honeypot_enabled ? honeypotValue : "",
218
- formLoadedAt: formLoadedAt()
224
+ formLoadedAt: formLoadedAt(),
225
+ agentInvoked
219
226
  });
220
227
  trackComplete();
221
228
  setIsComplete(true);
@@ -223,15 +230,19 @@ function FormClient({
223
230
  if (config.redirect_url) {
224
231
  window.location.href = config.redirect_url;
225
232
  }
233
+ const confirmation = typeof config.success_message === "string" && config.success_message.trim() ? config.success_message.trim() : DEFAULT_FORM_SUCCESS_MESSAGE;
234
+ return { status: "sent", message: confirmation, submissionId: data.submissionId || void 0 };
226
235
  } catch (error) {
227
236
  console.error("[Forms] Submission error:", error);
228
- setSubmitError("Something went wrong sending your submission. Please try again.");
237
+ const message = "Something went wrong sending your submission. Please try again.";
238
+ setSubmitError(message);
229
239
  onError?.(error);
240
+ return { status: "failed", message };
230
241
  } finally {
231
242
  isSubmittingRef.current = false;
232
243
  setIsSubmitting(false);
233
244
  }
234
- }, [config, honeypotValue, validateAll, buildVisibleValues, trackComplete, onSuccess, onError]);
245
+ }, [config, values, honeypotValue, validateAllFor, buildVisibleValues, trackComplete, onSuccess, onError]);
235
246
  const progress = useMemo(() => {
236
247
  return Math.round(step / config.total_steps * 100);
237
248
  }, [step, config.total_steps]);
@@ -316,13 +327,21 @@ function FormClient({
316
327
  "data-sk-state": isSubmitting ? "submitting" : "idle",
317
328
  "data-sk-ready": momentum.total > 0 && momentum.done >= momentum.total && step >= config.total_steps ? "true" : void 0,
318
329
  className: `sk-form ${className || ""}`,
330
+ ...formToolAttributes(config),
319
331
  method: "post",
320
332
  onSubmit: (e) => {
321
333
  e.preventDefault();
334
+ const agent = readAgentSubmit(e.nativeEvent);
322
335
  if (step < config.total_steps) {
323
- nextStep();
336
+ const moved = nextStep();
337
+ agent.respond(
338
+ Promise.resolve(
339
+ moved ? { status: "next_step", step: step + 1, totalSteps: config.total_steps } : { status: "invalid", errors: {} }
340
+ )
341
+ );
324
342
  } else {
325
- submit();
343
+ const domFills = unseenControlValues(e.currentTarget, config.fields, honeypotFieldName, values);
344
+ agent.respond(submit({ agentInvoked: agent.invoked, domFills }));
326
345
  }
327
346
  },
328
347
  children: [
@@ -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-3SDOQUUF.js';
5
- import { SITE_KIT_VERSION } from './chunk-AH5Q262S.js';
4
+ import { sonorFetch } from './chunk-D6ZC7XKZ.js';
5
+ import { SITE_KIT_VERSION } from './chunk-F24FNCTK.js';
6
6
 
7
7
  // src/website/design.ts
8
8
  var SITE_DESIGN_PATH = "/api/public/site/design";
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { sonorFetch } from './chunk-3SDOQUUF.js';
2
+ import { sonorFetch } from './chunk-D6ZC7XKZ.js';
3
3
  import { useState, useCallback, useEffect } from 'react';
4
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
5
 
@@ -3,7 +3,7 @@ import { registerAnalyticsDispatcher } from './chunk-LCDVPAMR.js';
3
3
  import { getOrCreateVisitorId, getOrCreateSessionId } from './chunk-24QZEO3Q.js';
4
4
  import { onPageLeave } from './chunk-L2V5PUFN.js';
5
5
  import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
6
- import { sonorFetch, sonorBeacon } from './chunk-3SDOQUUF.js';
6
+ import { sonorFetch, sonorBeacon } from './chunk-D6ZC7XKZ.js';
7
7
  import { createContext, useRef, useEffect, useState, useMemo, useCallback, useContext } from 'react';
8
8
  import { usePathname } from 'next/navigation';
9
9
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -157,7 +157,7 @@ function WebVitals({
157
157
  useEffect(() => {
158
158
  let cancelled = false;
159
159
  let teardown;
160
- import('./web-vitals.attribution-GD6LGLVF.js').then(({ onCLS, onLCP, onTTFB, onINP, onFCP }) => {
160
+ import('./web-vitals.attribution-N2PCCF4Q.js').then(({ onCLS, onLCP, onTTFB, onINP, onFCP }) => {
161
161
  if (cancelled) return;
162
162
  const { propApiUrl: propApiUrl2, propApiKey: propApiKey2, propSite: propSite2, allowInFrame: allowInFrame2, allowLocalhost: allowLocalhost2, debug: debug2, visitorId: visitorId2, sessionId: sessionId2, sessionTimeout: sessionTimeout2 } = propsRef.current;
163
163
  const sent = /* @__PURE__ */ new Map();
@@ -1,4 +1,4 @@
1
- import { SITE_KIT_VERSION } from './chunk-AH5Q262S.js';
1
+ import { SITE_KIT_VERSION } from './chunk-F24FNCTK.js';
2
2
 
3
3
  // src/shared/siteToken.ts
4
4
  var SITE_TOKEN_PREFIX = "st1.";
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+ import { DEFAULT_WIDGET_Z_INDEX } from './chunk-27FISYK4.js';
3
+ import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
4
+ import { useState, useEffect } from 'react';
5
+ import ReactDOM from 'react-dom';
6
+ import dynamic from 'next/dynamic';
7
+ import { jsx } from 'react/jsx-runtime';
8
+
9
+ var LazyChatWidget = dynamic(
10
+ () => import('./ChatWidget-XKOSH66S.js').then((m) => ({ default: m.ChatWidget })),
11
+ { ssr: false }
12
+ );
13
+ function SiteChat(props) {
14
+ const target = resolveAnalyticsTarget(props);
15
+ if (!target) return null;
16
+ return /* @__PURE__ */ jsx(IdleChat, { ...props });
17
+ }
18
+ function IdleChat({ projectId, position = "bottom-right", offsetBottom, zIndex = DEFAULT_WIDGET_Z_INDEX }) {
19
+ const [ready, setReady] = useState(false);
20
+ useEffect(() => {
21
+ const id = typeof requestIdleCallback !== "undefined" ? requestIdleCallback(() => setReady(true), { timeout: 3e3 }) : setTimeout(() => setReady(true), 1);
22
+ return () => {
23
+ typeof cancelIdleCallback !== "undefined" ? cancelIdleCallback(id) : clearTimeout(id);
24
+ };
25
+ }, []);
26
+ if (!ready || typeof document === "undefined") return null;
27
+ return ReactDOM.createPortal(
28
+ /* @__PURE__ */ jsx(
29
+ LazyChatWidget,
30
+ {
31
+ projectId: projectId ?? void 0,
32
+ config: {
33
+ position,
34
+ offsetBottom,
35
+ zIndex,
36
+ buttonColor: "#2563eb"
37
+ // Default blue-600 (AA on white), overridden by --sk-primary CSS var or API brand_primary
38
+ }
39
+ }
40
+ ),
41
+ document.body
42
+ );
43
+ }
44
+
45
+ export { SiteChat };
@@ -16,14 +16,64 @@ var MANAGED_LLM_SCHEMA_KNOWN_KEYS = /* @__PURE__ */ new Set([
16
16
  "speakable",
17
17
  "additionalType"
18
18
  ]);
19
+ var SENTENCE_SHARE = 0.4;
20
+ var ELLIPSIS = "\u2026";
21
+ var CLOSERS = /* @__PURE__ */ new Set(['"', "'", ")", "]", "\u201D", "\u2019", "\xBB"]);
22
+ var ABBREVIATIONS = /* @__PURE__ */ new Set([
23
+ "mr",
24
+ "mrs",
25
+ "ms",
26
+ "dr",
27
+ "prof",
28
+ "st",
29
+ "mt",
30
+ "ft",
31
+ "jr",
32
+ "sr",
33
+ "inc",
34
+ "ltd",
35
+ "co",
36
+ "corp",
37
+ "vs",
38
+ "no"
39
+ ]);
40
+ var TRAILING_SEPARATORS = /[\s.,;:\-\u2013\u2014/&([{]+$/;
41
+ function clipAtWordBoundary(text, max) {
42
+ const flat = text.replace(/\s+/g, " ").trim();
43
+ if (flat.length <= max) return flat;
44
+ if (max < 2) return flat.slice(0, Math.max(0, max));
45
+ const floor = Math.floor(max * (1 - SENTENCE_SHARE));
46
+ for (let end = max; end > floor; end--) {
47
+ if (endsSentence(flat, end)) return flat.slice(0, end);
48
+ }
49
+ const room = max - 1;
50
+ const space = flat.lastIndexOf(" ", room);
51
+ if (space <= 0) return `${flat.slice(0, room)}${ELLIPSIS}`;
52
+ if (endsSentence(flat, space)) return flat.slice(0, space);
53
+ const head = flat.slice(0, space).replace(TRAILING_SEPARATORS, "");
54
+ return `${head || flat.slice(0, room)}${ELLIPSIS}`;
55
+ }
56
+ function endsSentence(flat, end) {
57
+ if (flat[end] !== " ") return false;
58
+ let i = end - 1;
59
+ while (i > 0 && CLOSERS.has(flat[i])) i--;
60
+ const terminator = flat[i];
61
+ if (terminator === "!" || terminator === "?") return true;
62
+ return terminator === "." && !isAbbreviation(flat, i);
63
+ }
64
+ function isAbbreviation(flat, dot) {
65
+ let start = dot;
66
+ while (start > 0 && flat[start - 1] !== " ") start--;
67
+ const token = flat.slice(start, dot).replace(/^[("'[\u201c\u2018]+/, "").toLowerCase();
68
+ if (token.includes(".") || /^[a-z]$/.test(token)) return true;
69
+ return ABBREVIATIONS.has(token);
70
+ }
19
71
  function sanitizeLlmsPublicSummary(raw) {
20
72
  if (raw === null || raw === void 0) return null;
21
73
  if (typeof raw !== "string") return null;
22
74
  let s = raw.trim().replace(/\s+/g, " ");
23
75
  s = s.replace(/[\[\]\r\n]/g, " ").replace(/\s+/g, " ").trim();
24
- if (s.length > LLMS_PUBLIC_SUMMARY_MAX_LENGTH) {
25
- s = `${s.slice(0, LLMS_PUBLIC_SUMMARY_MAX_LENGTH - 1).trimEnd()}\u2026`;
26
- }
76
+ s = clipAtWordBoundary(s, LLMS_PUBLIC_SUMMARY_MAX_LENGTH);
27
77
  if (/\b[\w.%+-]+@[\w.-]+\.[A-Za-z]{2,}\b/.test(s)) return null;
28
78
  if (/\b\+?\d[\d\s().-]{8,}\b/.test(s)) return null;
29
79
  if (s.length < 8) return null;
@@ -34,9 +84,7 @@ function sanitizeLlmsDisclaimerLine(raw) {
34
84
  if (typeof raw !== "string") return null;
35
85
  let s = raw.trim().replace(/\s+/g, " ");
36
86
  s = s.replace(/[\[\]\r\n]/g, " ").replace(/\s+/g, " ").trim();
37
- if (s.length > LLMS_DISCLAIMER_MAX_LENGTH) {
38
- s = `${s.slice(0, LLMS_DISCLAIMER_MAX_LENGTH - 1).trimEnd()}\u2026`;
39
- }
87
+ s = clipAtWordBoundary(s, LLMS_DISCLAIMER_MAX_LENGTH);
40
88
  if (/\b[\w.%+-]+@[\w.-]+\.[A-Za-z]{2,}\b/.test(s)) return null;
41
89
  if (/\b\+?\d[\d\s().-]{8,}\b/.test(s)) return null;
42
90
  if (s.length < 3) return null;
@@ -94,4 +142,4 @@ function pickManagedLlmSchemaForJsonLd(schema) {
94
142
  return Object.keys(out).length ? out : null;
95
143
  }
96
144
 
97
- export { LLMS_DISCLAIMER_MAX_LENGTH, LLMS_PUBLIC_SUMMARY_MAX_LENGTH, LLM_GEO_CONTRACT_VERSION, MANAGED_LLM_SCHEMA_KNOWN_KEYS, isEmittableReviewNode, pickManagedLlmSchemaForJsonLd, sanitizeLlmsDisclaimerLine, sanitizeLlmsPublicSummary, sanitizePrimaryLanguageTag };
145
+ export { LLMS_DISCLAIMER_MAX_LENGTH, LLMS_PUBLIC_SUMMARY_MAX_LENGTH, LLM_GEO_CONTRACT_VERSION, MANAGED_LLM_SCHEMA_KNOWN_KEYS, clipAtWordBoundary, isEmittableReviewNode, pickManagedLlmSchemaForJsonLd, sanitizeLlmsDisclaimerLine, sanitizeLlmsPublicSummary, sanitizePrimaryLanguageTag };
@@ -0,0 +1,187 @@
1
+ 'use client';
2
+ import { useDeferredActivation } from './chunk-YC7ELZS3.js';
3
+ import { shellValuesToCarry } from './chunk-C2FZBUSS.js';
4
+ import { normalizeFormConfig } from './chunk-5SQK3D53.js';
5
+ import { DEFAULT_HONEYPOT_FIELD } from './chunk-NJCTQH2P.js';
6
+ import React, { useRef, useEffect, useState, useMemo, useCallback } from 'react';
7
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
+
9
+ var loadInteractiveForm = () => import('./ManagedForm-G6COGHXL.js').then((m) => m.ManagedForm);
10
+ var REPLAY_WAIT_MS = 8e3;
11
+ function FormEnhancer({
12
+ formId,
13
+ initialConfig,
14
+ className,
15
+ experience,
16
+ defaultValues,
17
+ onSuccess,
18
+ onError,
19
+ enter = "fade",
20
+ defer = true,
21
+ children
22
+ }) {
23
+ const idle = useDeferredActivation(defer);
24
+ const [engaged, setEngaged] = useState(false);
25
+ const hostRef = useRef(null);
26
+ const [Interactive, setInteractive] = useState(null);
27
+ const [carried, setCarried] = useState(null);
28
+ const pendingSubmitRef = useRef(false);
29
+ const [enhanced, setEnhanced] = useState(false);
30
+ const fields = useMemo(() => {
31
+ if (!initialConfig) return void 0;
32
+ try {
33
+ return normalizeFormConfig(initialConfig).fields;
34
+ } catch {
35
+ return void 0;
36
+ }
37
+ }, [initialConfig]);
38
+ const honeypotName = initialConfig && typeof initialConfig.honeypot_field === "string" && initialConfig.honeypot_field || DEFAULT_HONEYPOT_FIELD;
39
+ useEffect(() => {
40
+ if (Interactive) return;
41
+ const form = hostRef.current?.querySelector("form");
42
+ if (!form) return;
43
+ const onSubmit = (event) => {
44
+ event.preventDefault();
45
+ pendingSubmitRef.current = true;
46
+ setEngaged(true);
47
+ };
48
+ const onEngage = () => setEngaged(true);
49
+ form.addEventListener("submit", onSubmit);
50
+ form.addEventListener("focusin", onEngage);
51
+ form.addEventListener("input", onEngage);
52
+ form.addEventListener("change", onEngage);
53
+ for (const button of Array.from(form.querySelectorAll("button[data-sk-shell-submit]"))) {
54
+ button.disabled = false;
55
+ }
56
+ return () => {
57
+ form.removeEventListener("submit", onSubmit);
58
+ form.removeEventListener("focusin", onEngage);
59
+ form.removeEventListener("input", onEngage);
60
+ form.removeEventListener("change", onEngage);
61
+ };
62
+ }, [Interactive]);
63
+ useEffect(() => {
64
+ if (Interactive || !(idle || engaged)) return;
65
+ let cancelled = false;
66
+ loadInteractiveForm().then((Component) => {
67
+ if (cancelled) return;
68
+ const form = hostRef.current?.querySelector("form") ?? null;
69
+ const active = typeof document !== "undefined" ? document.activeElement : null;
70
+ const focusName = form && active && form.contains(active) ? active.name || null : null;
71
+ setCarried({ values: shellValuesToCarry(form, fields, honeypotName), focusName });
72
+ setInteractive(() => Component);
73
+ }).catch((err) => {
74
+ console.error("[Forms] Enhancement failed to load; keeping the form as rendered:", err);
75
+ });
76
+ return () => {
77
+ cancelled = true;
78
+ };
79
+ }, [Interactive, idle, engaged, fields, honeypotName]);
80
+ const onEnhancedMount = useCallback(() => {
81
+ setEnhanced(true);
82
+ const host = hostRef.current;
83
+ if (!host) return;
84
+ const focusName = carried?.focusName;
85
+ if (focusName) {
86
+ const target = Array.from(host.querySelectorAll("input, select, textarea")).find(
87
+ (el) => el.name === focusName
88
+ );
89
+ target?.focus({ preventScroll: true });
90
+ }
91
+ if (!pendingSubmitRef.current) return;
92
+ pendingSubmitRef.current = false;
93
+ const started = Date.now();
94
+ const tryReplay = () => {
95
+ const form = hostRef.current?.querySelector("form");
96
+ if (form) {
97
+ form.requestSubmit();
98
+ return;
99
+ }
100
+ if (Date.now() - started < REPLAY_WAIT_MS) requestAnimationFrame(tryReplay);
101
+ };
102
+ requestAnimationFrame(tryReplay);
103
+ }, [carried]);
104
+ const seededValues = useMemo(
105
+ () => carried && Object.keys(carried.values).length > 0 ? { ...defaultValues || {}, ...carried.values } : defaultValues,
106
+ [carried, defaultValues]
107
+ );
108
+ return /* @__PURE__ */ jsxs(
109
+ "div",
110
+ {
111
+ ref: hostRef,
112
+ "data-sk-form-mount": enhanced ? "enhanced" : "shell",
113
+ "data-sk-part": "form-mount",
114
+ "data-sk-carried": carried && Object.keys(carried.values).length > 0 ? "true" : void 0,
115
+ style: enter === "fade" ? FADE_HOST : void 0,
116
+ children: [
117
+ enter === "fade" && /* @__PURE__ */ jsx(EnterStyles, {}),
118
+ Interactive ? /* @__PURE__ */ jsx(Enhanced, { onMount: onEnhancedMount, fallback: children, children: /* @__PURE__ */ jsx(
119
+ Interactive,
120
+ {
121
+ formId,
122
+ initialConfig,
123
+ className,
124
+ experience,
125
+ defaultValues: seededValues,
126
+ onSuccess,
127
+ onError
128
+ }
129
+ ) }) : children
130
+ ]
131
+ }
132
+ );
133
+ }
134
+ var Enhanced = class extends React.Component {
135
+ constructor() {
136
+ super(...arguments);
137
+ this.state = { failed: false };
138
+ }
139
+ static getDerivedStateFromError() {
140
+ return { failed: true };
141
+ }
142
+ componentDidCatch(err) {
143
+ console.error("[Forms] Enhancement failed; keeping the native form:", err);
144
+ }
145
+ componentDidMount() {
146
+ if (!this.state.failed) this.props.onMount();
147
+ }
148
+ render() {
149
+ if (this.state.failed) return this.props.fallback;
150
+ return /* @__PURE__ */ jsx(FrameTag, { children: this.props.children });
151
+ }
152
+ };
153
+ function FrameTag({ children }) {
154
+ const ref = useRef(null);
155
+ useEffect(() => {
156
+ const el = ref.current;
157
+ if (!el) return;
158
+ el.setAttribute("data-sk-enter", "");
159
+ const id = requestAnimationFrame(() => el.removeAttribute("data-sk-enter"));
160
+ return () => cancelAnimationFrame(id);
161
+ }, []);
162
+ return /* @__PURE__ */ jsx("div", { ref, style: { display: "contents" }, children });
163
+ }
164
+ var FADE_HOST = {
165
+ transition: "opacity var(--sk-form-enter-duration, 320ms) var(--sk-form-enter-easing, cubic-bezier(.22,1,.36,1))"
166
+ };
167
+ function EnterStyles() {
168
+ return /* @__PURE__ */ jsx(
169
+ "style",
170
+ {
171
+ dangerouslySetInnerHTML: {
172
+ __html: `
173
+ [data-sk-part="form-mount"][data-sk-form-mount="shell"]{opacity:.55}
174
+ [data-sk-part="form-mount"][data-sk-form-mount="enhanced"]{opacity:1}
175
+ [data-sk-part="form-mount"] [data-sk-enter]{opacity:0;transform:translateY(var(--sk-form-enter-distance,6px))}
176
+ [data-sk-part="form-mount"] [data-sk-enter] *{transition:none!important}
177
+ @media (prefers-reduced-motion: reduce){
178
+ [data-sk-part="form-mount"]{transition:none!important}
179
+ [data-sk-part="form-mount"][data-sk-form-mount="shell"]{opacity:1}
180
+ [data-sk-part="form-mount"] [data-sk-enter]{opacity:1;transform:none}
181
+ }`
182
+ }
183
+ }
184
+ );
185
+ }
186
+
187
+ export { FormEnhancer };
@@ -1,5 +1,6 @@
1
1
  import { generateArticleAuthorNode, resolveArticleArtwork, withArticleAuthorIdentity } from './chunk-QXMIKDOL.js';
2
2
  import { resolveArticleUrl, createPublicationRoutes, resolvePublicationUrl, articleCategorySlug } from './chunk-WMVK77ND.js';
3
+ import { withoutSchemaPlaceholders } from './chunk-VWWITFWM.js';
3
4
  import { withSiteParam, withSiteBody } from './chunk-MJP63SSS.js';
4
5
  import { resolveServerApiConfig } from './chunk-X6F6SO4S.js';
5
6
  import { notFound } from 'next/navigation';
@@ -959,10 +960,10 @@ function generateBreadcrumbSchema(post, options = {}) {
959
960
  }
960
961
  function generateAllArticleSchemas(post, options = {}) {
961
962
  const schemas = [];
962
- const eeatSchema = post.schema ?? post.schema_json;
963
- if (eeatSchema) {
964
- const arr = Array.isArray(eeatSchema) ? eeatSchema : [eeatSchema];
965
- schemas.push(...arr.map((node) => withArticleAuthorIdentity(node, post.author, options)));
963
+ const eeatSchema = withoutSchemaPlaceholders(post.schema ?? post.schema_json).value;
964
+ const eeatNodes = eeatSchema ? Array.isArray(eeatSchema) ? eeatSchema : [eeatSchema] : [];
965
+ if (eeatNodes.length > 0) {
966
+ schemas.push(...eeatNodes.map((node) => withArticleAuthorIdentity(node, post.author, options)));
966
967
  } else {
967
968
  schemas.push(generateArticleSchema(post, options));
968
969
  const faqItems = post.faq_items ?? post.faqItems;