@sonordev/site-kit 8.3.0 → 8.4.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 (100) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/agent-manifest.json +11 -5
  3. package/dist/{AnalyticsProvider-QXO33JUF.js → AnalyticsProvider-D6GWEJSB.js} +5 -5
  4. package/dist/{ArticleViewTracker-KN6ENIRS.js → ArticleViewTracker-YZ76HM64.js} +5 -5
  5. package/dist/{BlocksPopup-3RPN6YVJ.js → BlocksPopup-UJXZ4AXG.js} +5 -5
  6. package/dist/{ChatWidget-D2X445PO.js → ChatWidget-H4EOSH5I.js} +5 -5
  7. package/dist/{FileField-UNGSNGJ5.js → FileField-CKBYGCNT.js} +3 -3
  8. package/dist/{FormSpotlight-JFH2ENKQ.js → FormSpotlight-CGF5ZE6S.js} +1 -1
  9. package/dist/{FormStage-RC3WHHXP.js → FormStage-PQPUPQWW.js} +1 -1
  10. package/dist/{ManagedForm-FOQZ2IYG.js → ManagedForm-YOEZ7CFS.js} +6 -6
  11. package/dist/{ManagedNewsletterForm-NFNF33V3.js → ManagedNewsletterForm-EUWJ2HCA.js} +18 -13
  12. package/dist/{SignalCore-5KABGX5C.js → SignalCore-JZSU7A44.js} +13 -7
  13. package/dist/{SiteChat-JQ46ZIJ4.js → SiteChat-OAZH34W7.js} +2 -2
  14. package/dist/SiteDesignReporter-IWBMZJYU.js +12 -0
  15. package/dist/{SitePopups-WZBKX5Q3.js → SitePopups-MLAT35UN.js} +5 -5
  16. package/dist/SitemapSync-6KQLKFO2.js +9 -0
  17. package/dist/_client/booking-widget.js +5 -5
  18. package/dist/affiliates/index.js +3 -3
  19. package/dist/analytics/index.js +5 -5
  20. package/dist/analytics/publication-cta.d.ts +3 -0
  21. package/dist/{article-styles-7QONZJR5.js → article-styles-AOOVVKXH.js} +4 -2
  22. package/dist/articles/index.js +4 -4
  23. package/dist/articles/server-ui.js +54 -53
  24. package/dist/articles/server.js +4 -4
  25. package/dist/chat/index.js +7 -7
  26. package/dist/{chunk-OIWFXUVD.js → chunk-3VSFOD2Z.js} +2 -2
  27. package/dist/{chunk-DPSNMOMZ.js → chunk-44GVU5UH.js} +1 -1
  28. package/dist/{chunk-QCZHYO5M.js → chunk-4HYIDQJJ.js} +1 -1
  29. package/dist/{chunk-C2UROFGZ.js → chunk-5S7YTOCB.js} +1 -1
  30. package/dist/{chunk-LPKKSYKX.js → chunk-5TDZHIG6.js} +3 -3
  31. package/dist/{chunk-X4IJS7LV.js → chunk-BLCHQIWR.js} +1 -1
  32. package/dist/{chunk-E3UR37RX.js → chunk-BLENFNBW.js} +2 -2
  33. package/dist/{chunk-TT4JZDKM.js → chunk-DKKYZLKR.js} +3 -3
  34. package/dist/{chunk-JO76F5WP.js → chunk-DLM4FFNH.js} +5 -1
  35. package/dist/{chunk-2JMCBJM5.js → chunk-DS6LECIX.js} +1 -1
  36. package/dist/{chunk-SCTVADDL.js → chunk-HH2GOZTO.js} +2 -2
  37. package/dist/{chunk-GKJVSZ7H.js → chunk-ITY4UZEJ.js} +3 -3
  38. package/dist/{chunk-XLAXYPTK.js → chunk-J4KX2D3Q.js} +3 -3
  39. package/dist/{chunk-BOCYBDKV.js → chunk-JCSU6EJI.js} +1 -1
  40. package/dist/{chunk-LBDWAXAO.js → chunk-JHTPHA2D.js} +5 -2
  41. package/dist/{chunk-MQ4REMTH.js → chunk-JV5YWTJN.js} +12 -3
  42. package/dist/{chunk-QDILCQBL.js → chunk-KASKB7UK.js} +11 -10
  43. package/dist/{chunk-5RYSTKVU.js → chunk-LEGMPX6N.js} +1 -1
  44. package/dist/{chunk-WSOZ3FQN.js → chunk-LPVEBQVL.js} +1 -1
  45. package/dist/{chunk-EA6FZMYA.js → chunk-MTV5LYXN.js} +31 -2
  46. package/dist/{chunk-U7Y3QG3A.js → chunk-MZX4EN6Z.js} +1 -1
  47. package/dist/{chunk-3OY52UFM.js → chunk-OKITZFML.js} +1 -1
  48. package/dist/{chunk-QBCWVT2S.js → chunk-PEI6DRSK.js} +2 -2
  49. package/dist/{chunk-HND6BDXC.js → chunk-QD73RO67.js} +2 -2
  50. package/dist/{chunk-4PVMUPC6.js → chunk-QKOVP7WO.js} +1 -1
  51. package/dist/{chunk-C4AFNZT2.js → chunk-RBNHRZGD.js} +1 -1
  52. package/dist/{chunk-WBUG7XRL.js → chunk-S6QDNXDK.js} +1 -1
  53. package/dist/{chunk-KJVUOJMW.js → chunk-TY6WNQO3.js} +150 -67
  54. package/dist/{chunk-SNRE3ZVZ.js → chunk-UVYUTBT4.js} +11 -1
  55. package/dist/{chunk-FHUTOXS7.js → chunk-XENW66OF.js} +1 -1
  56. package/dist/{chunk-IEGDPRQB.js → chunk-XI2XM7UG.js} +1 -1
  57. package/dist/{chunk-NA4Z4CQG.js → chunk-YM5KMNTU.js} +1 -1
  58. package/dist/client/index.js +4 -4
  59. package/dist/fleet/index.js +5 -5
  60. package/dist/forms/index.js +8 -8
  61. package/dist/forms/server.js +2 -2
  62. package/dist/forms/types.d.ts +5 -0
  63. package/dist/forms/useForm.d.ts +4 -0
  64. package/dist/images/index.js +4 -4
  65. package/dist/index.js +1 -1
  66. package/dist/layout/client.js +8 -8
  67. package/dist/layout/index.js +9 -9
  68. package/dist/llms/index.js +4 -4
  69. package/dist/maps/index.js +3 -3
  70. package/dist/mcp/sonor.d.ts +4 -0
  71. package/dist/mcp/sonor.js +33 -16
  72. package/dist/press-kit/articles/artwork.d.ts +3 -0
  73. package/dist/press-kit/articles/index.d.ts +1 -0
  74. package/dist/press-kit/articles/news-sitemap.d.ts +4 -10
  75. package/dist/press-kit/articles/public-reads.d.ts +18 -2
  76. package/dist/press-kit/articles/publication-definition.d.ts +41 -0
  77. package/dist/press-kit/articles/publication-reads.d.ts +54 -0
  78. package/dist/press-kit/articles/server-core.d.ts +21 -3
  79. package/dist/press-kit/articles/types.d.ts +23 -0
  80. package/dist/revalidate/index.js +2 -2
  81. package/dist/runtime/index.js +3 -3
  82. package/dist/seo/client.js +5 -5
  83. package/dist/seo/index.js +5 -5
  84. package/dist/seo/llms.js +4 -4
  85. package/dist/seo/register-sitemap-cli.js +1 -1
  86. package/dist/seo/sitemap.js +2 -2
  87. package/dist/server/index.js +2 -2
  88. package/dist/shared/reporting-gate.d.ts +2 -0
  89. package/dist/shared/version.d.ts +1 -1
  90. package/dist/signal/index.js +2 -2
  91. package/dist/sitemap/index.js +2 -2
  92. package/dist/sync/index.js +5 -5
  93. package/dist/website/images.js +4 -4
  94. package/dist/website/index.js +6 -6
  95. package/dist/website/popups.js +5 -5
  96. package/dist/{writeLLMsTxt-VKHCM5IS.js → writeLLMsTxt-N4Y4YC2H.js} +1 -1
  97. package/package.json +1 -1
  98. package/dist/SiteDesignReporter-N7L6DR4J.js +0 -12
  99. package/dist/SitemapSync-GY6UADRI.js +0 -9
  100. /package/dist/{chunk-D6ZIQMHB.js → chunk-CE7VPAEH.js} +0 -0
@@ -2,12 +2,23 @@
2
2
  import { registerAnalyticsDispatcher } from './chunk-LCDVPAMR.js';
3
3
  import { getOrCreateVisitorId, getOrCreateSessionId } from './chunk-ZKADJNR2.js';
4
4
  import { onPageLeave } from './chunk-L2V5PUFN.js';
5
- import { resolveAnalyticsTarget } from './chunk-JO76F5WP.js';
6
- import { sonorFetch, sonorBeacon } from './chunk-OIWFXUVD.js';
5
+ import { resolveAnalyticsTarget } from './chunk-DLM4FFNH.js';
6
+ import { sonorFetch, sonorBeacon } from './chunk-3VSFOD2Z.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';
10
10
 
11
+ // src/analytics/publication-cta.ts
12
+ function publicationCtaEvent(target) {
13
+ if (!(target instanceof Element)) return null;
14
+ const link = target.closest("a[data-sk-article-id][data-sk-article-goal]");
15
+ const article_id = link?.dataset.skArticleId?.trim();
16
+ const goal = link?.dataset.skArticleGoal?.trim();
17
+ const href = link?.getAttribute("href")?.trim();
18
+ if (!article_id || !goal || !href || !/^(?:https?:\/\/|\/(?!\/))/.test(href)) return null;
19
+ return { name: "article_cta_click", category: "publication", properties: { article_id, goal, href } };
20
+ }
21
+
11
22
  // src/analytics/page-observation.ts
12
23
  function collectPageObservation() {
13
24
  const observedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -978,6 +989,24 @@ function AnalyticsProvider({
978
989
  document.removeEventListener("click", handleClick);
979
990
  };
980
991
  }, [pathname, gate, trackClicks, debug]);
992
+ useEffect(() => {
993
+ if (typeof document === "undefined" || !resolveAnalyticsTarget(gate)) return;
994
+ const click = (event) => {
995
+ const options = publicationCtaEvent(event.target);
996
+ if (!options) return;
997
+ void analyticsSend("/api/public/analytics/event", {
998
+ sessionId: sessionIdRef.current,
999
+ visitorId: visitorIdRef.current,
1000
+ site,
1001
+ pagePath: pathname,
1002
+ eventName: options.name,
1003
+ eventCategory: options.category,
1004
+ properties: options.properties
1005
+ }, { ...gate, keepalive: true });
1006
+ };
1007
+ document.addEventListener("click", click, { passive: true });
1008
+ return () => document.removeEventListener("click", click);
1009
+ }, [gate, site, pathname]);
981
1010
  const trackEvent = useCallback((options) => {
982
1011
  const doTrack = async () => {
983
1012
  const event = {
@@ -1,5 +1,5 @@
1
1
  import { resolveSiteHost } from './chunk-FESKKJAS.js';
2
- import { configureSiteCredential, currentCredential } from './chunk-C4AFNZT2.js';
2
+ import { configureSiteCredential, currentCredential } from './chunk-RBNHRZGD.js';
3
3
 
4
4
  // src/shared/publishCredential.ts
5
5
  function publishSiteCredential({
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { sonorFetch } from './chunk-OIWFXUVD.js';
2
+ import { sonorFetch } from './chunk-3VSFOD2Z.js';
3
3
  import { useState, useCallback, useEffect } from 'react';
4
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
5
 
@@ -1,8 +1,8 @@
1
1
  'use client';
2
- import { getSiteDesignTokens, tokensForReport, shouldReportDesign, sendSiteDesign } from './chunk-TT4JZDKM.js';
2
+ import { getSiteDesignTokens, tokensForReport, shouldReportDesign, sendSiteDesign } from './chunk-DKKYZLKR.js';
3
3
  import { designTokensHash } from './chunk-IHG36STL.js';
4
4
  import { useDeferredActivation } from './chunk-YC7ELZS3.js';
5
- import { resolveAnalyticsTarget } from './chunk-JO76F5WP.js';
5
+ import { resolveAnalyticsTarget } from './chunk-DLM4FFNH.js';
6
6
  import { useRef, useEffect } from 'react';
7
7
 
8
8
  var STAMP_KEY = (site) => `_sk_design_sent:${site}`;
@@ -2,9 +2,9 @@
2
2
  import { VISUAL_VIEWPORT_GAP_PROPERTY } from './chunk-6T6CQVNL.js';
3
3
  import { GlassStyles } from './chunk-JE4A5S4F.js';
4
4
  import { DEFAULT_WIDGET_Z_INDEX } from './chunk-27FISYK4.js';
5
- import { useForm } from './chunk-MQ4REMTH.js';
5
+ import { useForm } from './chunk-JV5YWTJN.js';
6
6
  import { parseColor, relativeLuminance, DARK_LUMINANCE, contrastRatio } from './chunk-S22FSH7C.js';
7
- import { sonorFetch } from './chunk-OIWFXUVD.js';
7
+ import { sonorFetch } from './chunk-3VSFOD2Z.js';
8
8
  import { readPublishedApiConfig } from './chunk-W7OPEMGJ.js';
9
9
  import { useState, useEffect, useRef, useLayoutEffect, useCallback, Fragment as Fragment$1 } from 'react';
10
10
  import { lexer } from 'marked';
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { detectTimezone, releaseSlotHold, fetchBookingTypeDetails, fetchBookingTypes, fetchAvailability, createSlotHold, createBooking, formatDuration, formatDate, formatTime, prepareMeeting } from './chunk-WBUG7XRL.js';
2
+ import { detectTimezone, releaseSlotHold, fetchBookingTypeDetails, fetchBookingTypes, fetchAvailability, createSlotHold, createBooking, formatDuration, formatDate, formatTime, prepareMeeting } from './chunk-S6QDNXDK.js';
3
3
  import { readPublishedApiConfig, DEFAULT_SONOR_API_URL } from './chunk-W7OPEMGJ.js';
4
4
  import { useState, useRef, useMemo, useCallback, useEffect, useId } from 'react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { SITE_KIT_VERSION } from './chunk-C2UROFGZ.js';
1
+ import { SITE_KIT_VERSION } from './chunk-5S7YTOCB.js';
2
2
 
3
3
  // src/shared/siteToken.ts
4
4
  var SITE_TOKEN_PREFIX = "st1.";
@@ -1,4 +1,4 @@
1
- import { sonorFetch } from './chunk-OIWFXUVD.js';
1
+ import { sonorFetch } from './chunk-3VSFOD2Z.js';
2
2
  import { getClientApiConfig } from './chunk-W7OPEMGJ.js';
3
3
 
4
4
  // src/sync/api.ts
@@ -1,11 +1,75 @@
1
1
  import { insertArticlePhotos } from './chunk-M5SF7NCC.js';
2
- import { generateArticleAuthorNode, resolveArticleArtwork, withArticleAuthorIdentity } from './chunk-SNRE3ZVZ.js';
2
+ import { resolveRepresentativeImage, generateArticleAuthorNode, articleImageSchema, resolveArticleArtwork, withArticleAuthorIdentity } from './chunk-UVYUTBT4.js';
3
3
  import { resolveArticleUrl, createPublicationRoutes, resolvePublicationUrl, articleCategorySlug } from './chunk-4K735O3J.js';
4
4
  import { withoutSchemaPlaceholders } from './chunk-VWWITFWM.js';
5
5
  import { withSiteParam, withSiteBody } from './chunk-MJP63SSS.js';
6
6
  import { resolveServerApiConfig } from './chunk-C7TLGL3R.js';
7
+ import 'server-only';
7
8
  import { notFound } from 'next/navigation';
8
9
 
10
+ // packages/press-kit/src/articles/public-reads.ts
11
+ var ArticleUnavailableError = class extends Error {
12
+ constructor(status) {
13
+ super("Articles are temporarily unavailable. Please try again.");
14
+ this.status = status;
15
+ this.code = "ARTICLE_UNAVAILABLE";
16
+ this.name = "ArticleUnavailableError";
17
+ }
18
+ };
19
+ async function readArticleBySlug({ apiUrl, apiKey, site }, slug) {
20
+ try {
21
+ const response = await fetch(withSiteParam(`${apiUrl}/public/articles/posts/${encodeURIComponent(slug)}`, site), {
22
+ headers: { "x-api-key": apiKey },
23
+ next: { revalidate: 60 }
24
+ });
25
+ if (response.status === 404) return { state: "not_found" };
26
+ if (!response.ok) return { state: "unavailable", status: response.status };
27
+ const data = await response.json();
28
+ if (!data.post || typeof data.post.slug !== "string" || typeof data.post.title !== "string") return { state: "unavailable" };
29
+ return { state: "ready", value: data.post };
30
+ } catch {
31
+ return { state: "unavailable" };
32
+ }
33
+ }
34
+ async function fetchArticleBySlug(config, slug) {
35
+ const result = await readArticleBySlug(config, slug);
36
+ if (result.state === "unavailable") throw new ArticleUnavailableError(result.status);
37
+ return result.state === "ready" ? result.value : null;
38
+ }
39
+ async function fetchArticleCategories({ apiUrl, apiKey, site }) {
40
+ try {
41
+ const response = await fetch(withSiteParam(`${apiUrl}/public/articles/categories`, site), {
42
+ headers: { "x-api-key": apiKey },
43
+ next: { revalidate: 300 }
44
+ });
45
+ if (!response.ok) return [];
46
+ const data = await response.json();
47
+ return data.categories || [];
48
+ } catch (error) {
49
+ console.error("[Articles] Error fetching categories:", error);
50
+ return [];
51
+ }
52
+ }
53
+ async function fetchRelatedArticles({ apiUrl, apiKey, site }, currentPostId, limit, cache = {}) {
54
+ try {
55
+ const response = await fetch(`${apiUrl}/public/articles/related`, {
56
+ method: "POST",
57
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
58
+ body: JSON.stringify(withSiteBody({ current_post_id: currentPostId, limit }, site)),
59
+ ...cache.revalidate !== void 0 ? { next: { revalidate: cache.revalidate } } : {}
60
+ });
61
+ if (!response.ok) {
62
+ console.error("[Articles] Failed to fetch related posts:", response.statusText);
63
+ return [];
64
+ }
65
+ const data = await response.json();
66
+ return data.posts || [];
67
+ } catch (error) {
68
+ console.error("[Articles] Error fetching related posts:", error);
69
+ return [];
70
+ }
71
+ }
72
+
9
73
  // packages/press-kit/src/articles/excerpt.ts
10
74
  function sanitizeExcerpt(raw, maxLength = 160) {
11
75
  if (!raw) return "";
@@ -59,8 +123,11 @@ function buildNewsSitemapXml(posts, options, now = /* @__PURE__ */ new Date()) {
59
123
  const language = options.language || "en";
60
124
  const items = recentNewsPosts(posts, now).map((post) => {
61
125
  const url = resolveArticleUrl(post, { ...options, siteUrl: options.siteUrl });
126
+ const image = resolveRepresentativeImage(post);
127
+ const imageUrl = image ? new URL(image.url, options.siteUrl).href : void 0;
62
128
  return ` <url>
63
- <loc>${escapeXml(url)}</loc>
129
+ <loc>${escapeXml(url)}</loc>${imageUrl ? `
130
+ <image:image><image:loc>${escapeXml(imageUrl)}</image:loc></image:image>` : ""}
64
131
  <news:news>
65
132
  <news:publication>
66
133
  <news:name>${escapeXml(options.publicationName)}</news:name>
@@ -72,63 +139,79 @@ function buildNewsSitemapXml(posts, options, now = /* @__PURE__ */ new Date()) {
72
139
  </url>`;
73
140
  });
74
141
  return `<?xml version="1.0" encoding="UTF-8"?>
75
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
142
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
76
143
  ${items.join("\n")}${items.length ? "\n" : ""}</urlset>`;
77
144
  }
78
-
79
- // packages/press-kit/src/articles/public-reads.ts
80
- async function fetchArticleBySlug({ apiUrl, apiKey, site }, slug) {
145
+ async function publicRead(path, site) {
146
+ const config = resolveServerApiConfig();
147
+ if (!config) return { state: "unavailable" };
81
148
  try {
82
- const response = await fetch(withSiteParam(`${apiUrl}/public/articles/posts/${slug}`, site), {
83
- headers: { "x-api-key": apiKey },
149
+ const response = await fetch(withSiteParam(`${config.apiUrl}${path}`, site), {
150
+ headers: { "x-api-key": config.apiKey },
84
151
  next: { revalidate: 60 }
85
152
  });
86
- if (!response.ok) {
87
- if (response.status !== 404) console.error("[Articles] Failed to fetch post:", response.statusText);
88
- return null;
89
- }
90
- const data = await response.json();
91
- return data.post || null;
92
- } catch (error) {
93
- console.error("[Articles] Error fetching post:", error);
94
- return null;
153
+ if (response.status === 404) return { state: "not_found" };
154
+ if (!response.ok) return { state: "unavailable", status: response.status };
155
+ return { state: "ready", value: await response.json() };
156
+ } catch {
157
+ return { state: "unavailable" };
95
158
  }
96
159
  }
97
- async function fetchArticleCategories({ apiUrl, apiKey, site }) {
98
- try {
99
- const response = await fetch(withSiteParam(`${apiUrl}/public/articles/categories`, site), {
100
- headers: { "x-api-key": apiKey },
101
- next: { revalidate: 300 }
102
- });
103
- if (!response.ok) return [];
104
- const data = await response.json();
105
- return data.categories || [];
106
- } catch (error) {
107
- console.error("[Articles] Error fetching categories:", error);
108
- return [];
109
- }
160
+ async function getPublicationIssues(options = {}) {
161
+ const query = new URLSearchParams({ limit: String(Math.min(50, Math.max(1, options.limit || 20))) });
162
+ if (options.cursor) query.set("cursor", options.cursor);
163
+ const result = await publicRead(`/public/publications/issues?${query}`, options.site);
164
+ if (result.state === "ready" && !Array.isArray(result.value?.issues)) return { state: "unavailable" };
165
+ return result;
110
166
  }
111
- async function fetchRelatedArticles({ apiUrl, apiKey, site }, currentPostId, limit, cache = {}) {
167
+ async function getPublicationIssue(id, options = {}) {
168
+ const result = await publicRead(`/public/publications/issues/${encodeURIComponent(id)}`, options.site);
169
+ if (result.state !== "ready") return result;
170
+ return result.value?.issue?.id ? { state: "ready", value: result.value.issue } : { state: "unavailable" };
171
+ }
172
+ var PUBLICATION_PREVIEW_COOKIE = "_sk_press_preview";
173
+ var PUBLICATION_PREVIEW_HEADERS = { "Cache-Control": "private, no-store, max-age=0", "X-Robots-Tag": "noindex, nofollow, noarchive", "Referrer-Policy": "no-referrer" };
174
+ async function getArticlePreview(token, options) {
175
+ const config = resolveServerApiConfig();
176
+ if (!config || !token || token.length > 8192 || !options.site) return { state: "not_found" };
112
177
  try {
113
- const response = await fetch(`${apiUrl}/public/articles/related`, {
178
+ const response = await fetch(`${config.apiUrl}/public/articles/preview`, {
114
179
  method: "POST",
115
- headers: { "Content-Type": "application/json", "x-api-key": apiKey },
116
- body: JSON.stringify(withSiteBody({ current_post_id: currentPostId, limit }, site)),
117
- ...cache.revalidate !== void 0 ? { next: { revalidate: cache.revalidate } } : {}
180
+ headers: { "Content-Type": "application/json", "x-api-key": config.apiKey },
181
+ body: JSON.stringify({ token, site: options.site }),
182
+ cache: "no-store"
118
183
  });
119
- if (!response.ok) {
120
- console.error("[Articles] Failed to fetch related posts:", response.statusText);
121
- return [];
122
- }
123
- const data = await response.json();
124
- return data.posts || [];
125
- } catch (error) {
126
- console.error("[Articles] Error fetching related posts:", error);
127
- return [];
184
+ if ([401, 403, 404, 410].includes(response.status)) return { state: "not_found" };
185
+ if (!response.ok) return { state: "unavailable", status: response.status };
186
+ const value = await response.json();
187
+ if (!value.post?.slug || value.site !== options.site || !Number.isFinite(Date.parse(value.expiresAt)) || Date.parse(value.expiresAt) <= Date.now()) return { state: "not_found" };
188
+ return { state: "ready", value };
189
+ } catch {
190
+ return { state: "unavailable" };
128
191
  }
129
192
  }
130
-
131
- // packages/press-kit/src/articles/server-core.ts
193
+ function createPublicationPreviewHandler(options) {
194
+ const origin = new URL(options.siteUrl);
195
+ if (!/^https?:$/.test(origin.protocol) || !/^\/(?!\/)[^?#\\]*$/.test(options.previewPath)) throw new Error("Configure an HTTP site origin and a local preview path.");
196
+ return async (request) => {
197
+ const token = new URL(request.url).searchParams.get("token") || "";
198
+ const result = await getArticlePreview(token, { site: origin.hostname });
199
+ if (result.state !== "ready") return new Response(result.state === "unavailable" ? "Preview is temporarily unavailable. Please try again." : "This preview has expired or changed. Request a new preview in Sonor.", {
200
+ status: result.state === "unavailable" ? 503 : 410,
201
+ headers: PUBLICATION_PREVIEW_HEADERS
202
+ });
203
+ const maxAge = Math.max(0, Math.min(900, Math.floor((Date.parse(result.value.expiresAt) - Date.now()) / 1e3)));
204
+ return new Response(null, { status: 303, headers: {
205
+ ...PUBLICATION_PREVIEW_HEADERS,
206
+ Location: new URL(options.previewPath, origin.origin).href,
207
+ "Set-Cookie": `${PUBLICATION_PREVIEW_COOKIE}=${encodeURIComponent(token)}; Path=${options.previewPath}; Max-Age=${maxAge}; HttpOnly; SameSite=Lax${origin.protocol === "https:" ? "; Secure" : ""}`
208
+ } });
209
+ };
210
+ }
211
+ async function getArticleResult(slug, options) {
212
+ const config = resolveServerApiConfig();
213
+ return config ? readArticleBySlug({ ...config, site: options?.site }, slug) : { state: "unavailable" };
214
+ }
132
215
  var SEO_LIMITS = {
133
216
  title: { min: 30, max: 60, recommended: 55 },
134
217
  metaDescription: { min: 120, max: 160, recommended: 155 },
@@ -221,8 +304,7 @@ function validateArticleSeo(post) {
221
304
  async function getArticle(slug, options) {
222
305
  const config = resolveServerApiConfig();
223
306
  if (!config) {
224
- console.warn("[Articles] No API key configured");
225
- return null;
307
+ throw new ArticleUnavailableError();
226
308
  }
227
309
  return fetchArticleBySlug({ ...config, site: options?.site }, slug);
228
310
  }
@@ -269,11 +351,21 @@ async function generateArticleMetadata(slug, options = {}) {
269
351
  }
270
352
  const title = post.meta_title || post.title;
271
353
  const description = sanitizeExcerpt(post.meta_description || post.excerpt);
354
+ const representative = options.images === false ? void 0 : resolveRepresentativeImage(post);
272
355
  const image = options.images === false ? void 0 : post.featured_image || defaultImage;
356
+ const images = options.images === false ? [] : [
357
+ ...representative ? [representative] : [],
358
+ ...image && image !== representative?.url ? [{
359
+ url: image,
360
+ alt: post.featured_image_alt || post.title,
361
+ ...post.featured_image_width && post.featured_image_height ? { width: post.featured_image_width, height: post.featured_image_height } : {}
362
+ }] : []
363
+ ];
273
364
  const url = resolveArticleUrl(post, options);
274
365
  return {
275
366
  title,
276
367
  description,
368
+ robots: { "max-image-preview": "large" },
277
369
  openGraph: {
278
370
  title: post.og_title || title,
279
371
  description: post.og_description || description,
@@ -282,16 +374,7 @@ async function generateArticleMetadata(slug, options = {}) {
282
374
  type: "article",
283
375
  publishedTime: post.published_at,
284
376
  authors: post.author ? [typeof post.author === "string" ? post.author : post.author.name] : void 0,
285
- ...image && {
286
- images: [
287
- {
288
- url: image,
289
- width: 1200,
290
- height: 630,
291
- alt: post.featured_image_alt || post.title
292
- }
293
- ]
294
- }
377
+ ...images.length ? { images } : {}
295
378
  },
296
379
  twitter: {
297
380
  card: "summary_large_image",
@@ -415,7 +498,7 @@ async function generateArticleSitemap(siteUrl, options = {}) {
415
498
  });
416
499
  slugs.forEach((post) => {
417
500
  const isPillar = post.article_type === "pillar";
418
- const image = absoluteImage(post.featured_image, siteUrl);
501
+ const image = absoluteImage(resolveRepresentativeImage(post)?.url, siteUrl);
419
502
  entries.push({
420
503
  url: resolveArticleUrl(post, { ...options, siteUrl }),
421
504
  lastModified: post.last_modified ? new Date(post.last_modified) : void 0,
@@ -443,7 +526,7 @@ function generateArticleSchema(post, options = {}) {
443
526
  };
444
527
  const author = generateArticleAuthorNode(post.author, options);
445
528
  if (author) schema.author = author;
446
- const articleImage = resolveArticleArtwork(post, "article").src;
529
+ const articleImage = articleImageSchema(post);
447
530
  if (articleImage) schema.image = articleImage;
448
531
  if (siteName || logoUrl) {
449
532
  schema.publisher = {
@@ -520,8 +603,7 @@ async function getAllArticles(options = {}) {
520
603
  if (!(maxPosts > 0)) return [];
521
604
  const config = resolveServerApiConfig();
522
605
  if (!config) {
523
- console.warn("[Articles] No API key configured");
524
- return [];
606
+ throw new ArticleUnavailableError();
525
607
  }
526
608
  const { apiUrl, apiKey } = config;
527
609
  const posts = [];
@@ -535,15 +617,16 @@ async function getAllArticles(options = {}) {
535
617
  next: { revalidate: 300 }
536
618
  }
537
619
  );
538
- if (!response.ok) break;
620
+ if (!response.ok) throw new ArticleUnavailableError(response.status);
539
621
  const data = await response.json();
540
- const batch = data.posts || [];
622
+ if (!Array.isArray(data.posts)) throw new ArticleUnavailableError();
623
+ const batch = data.posts;
541
624
  posts.push(...batch);
542
625
  if (!batch.length || !data.pagination?.has_next) break;
543
626
  page += 1;
544
627
  }
545
628
  } catch (error) {
546
- console.error("[Articles] Error fetching posts for RSS:", error);
629
+ throw error instanceof ArticleUnavailableError ? error : new ArticleUnavailableError();
547
630
  }
548
631
  return posts.slice(0, maxPosts);
549
632
  }
@@ -812,7 +895,7 @@ function generateClusterArticleSchema(post, cluster, options = {}) {
812
895
  };
813
896
  const author = generateArticleAuthorNode(post.author, options);
814
897
  if (author) schema.author = author;
815
- const articleImage = resolveArticleArtwork(post, "article").src;
898
+ const articleImage = articleImageSchema(post);
816
899
  if (articleImage) schema.image = articleImage;
817
900
  if (siteName || logoUrl) {
818
901
  schema.publisher = {
@@ -1083,4 +1166,4 @@ function generateAuthorPageMetadata(author, options = {}) {
1083
1166
  };
1084
1167
  }
1085
1168
 
1086
- export { FEED_DEFAULT_ITEMS, NEWS_SITEMAP_MAX_URLS, NEWS_SITEMAP_WINDOW_MS, SEO_LIMITS, buildNewsSitemapXml, cdata, fetchArticleBySlug, fetchArticleCategories, fetchRelatedArticles, generateAllArticleSchemas, generateArticleCategoryMetadata, generateArticleListSchema, generateArticleMetadata, generateArticleSchema, generateArticleSitemap, generateArticleStaticParams, generateAtomFeed, generateAuthorPageMetadata, generateAuthorStaticParams, generateBreadcrumbSchema, generateCategoryStaticParams, generateClusterArticleSchema, generateClusterBreadcrumbSchema, generateClusterItemListSchema, generateClusterLandingSchema, generateClusterPageMetadata, generateFaqSchema, generateHowToSchema, generateNewsSitemap, generatePublicationMetadata, generateRssFeed, getAllArticleSlugs, getAllArticles, getAllAuthorSlugs, getArticle, getArticleCategories, getAuthorBySlug, getAuthorPosts, getClusterNavigation, getPostsByCategory, getRelatedInsights, getTopicCluster, getTopicClusters, recentNewsPosts, requireArticle, sanitizeExcerpt, validateArticleSeo, validateMetaDescription, validateSeoTitle };
1169
+ export { ArticleUnavailableError, FEED_DEFAULT_ITEMS, NEWS_SITEMAP_MAX_URLS, NEWS_SITEMAP_WINDOW_MS, PUBLICATION_PREVIEW_COOKIE, PUBLICATION_PREVIEW_HEADERS, SEO_LIMITS, buildNewsSitemapXml, cdata, createPublicationPreviewHandler, fetchArticleBySlug, fetchArticleCategories, fetchRelatedArticles, generateAllArticleSchemas, generateArticleCategoryMetadata, generateArticleListSchema, generateArticleMetadata, generateArticleSchema, generateArticleSitemap, generateArticleStaticParams, generateAtomFeed, generateAuthorPageMetadata, generateAuthorStaticParams, generateBreadcrumbSchema, generateCategoryStaticParams, generateClusterArticleSchema, generateClusterBreadcrumbSchema, generateClusterItemListSchema, generateClusterLandingSchema, generateClusterPageMetadata, generateFaqSchema, generateHowToSchema, generateNewsSitemap, generatePublicationMetadata, generateRssFeed, getAllArticleSlugs, getAllArticles, getAllAuthorSlugs, getArticle, getArticleCategories, getArticlePreview, getArticleResult, getAuthorBySlug, getAuthorPosts, getClusterNavigation, getPostsByCategory, getPublicationIssue, getPublicationIssues, getRelatedInsights, getTopicCluster, getTopicClusters, recentNewsPosts, requireArticle, sanitizeExcerpt, validateArticleSeo, validateMetaDescription, validateSeoTitle };
@@ -17,6 +17,16 @@ function resolveArticleArtwork(post, placement = "card") {
17
17
  function articleArtworkImageStyle(artwork) {
18
18
  return artwork.composed ? { display: "block", height: "auto", maxHeight: "none", objectFit: "contain", transform: "none" } : void 0;
19
19
  }
20
+ function resolveRepresentativeImage(post) {
21
+ const image = post.representative_image?.url ? post.representative_image : post.editorial_image ? { url: post.editorial_image, alt: post.editorial_image_alt, width: post.editorial_image_width, height: post.editorial_image_height } : post.featured_image ? { url: post.featured_image, alt: post.featured_image_alt, width: post.featured_image_width, height: post.featured_image_height } : void 0;
22
+ if (!image || !/^(https?:\/\/|\/(?!\/))/i.test(image.url)) return void 0;
23
+ const dimensions = Number.isFinite(image.width) && Number.isFinite(image.height) && image.width > 0 && image.height > 0;
24
+ return { url: image.url, alt: image.alt ?? post.title ?? "", ...dimensions ? { width: image.width, height: image.height } : {} };
25
+ }
26
+ function articleImageSchema(post) {
27
+ const image = resolveRepresentativeImage(post);
28
+ return image ? image.width ? { "@type": "ImageObject", url: image.url, width: image.width, height: image.height } : image.url : void 0;
29
+ }
20
30
 
21
31
  // packages/press-kit/src/articles/author-schema.ts
22
32
  var ARTICLE_TYPES = /* @__PURE__ */ new Set(["Article", "BlogPosting", "NewsArticle", "TechArticle", "Report", "ScholarlyArticle"]);
@@ -122,4 +132,4 @@ function withArticleAuthorIdentity(node, author, options = {}) {
122
132
  return { ...record, author: { ...byline, ...additions } };
123
133
  }
124
134
 
125
- export { articleArtworkImageStyle, authorEntityId, generateArticleAuthorNode, generateAuthorPersonSchema, generateAuthorSchema, resolveArticleArtwork, withArticleAuthorIdentity };
135
+ export { articleArtworkImageStyle, articleImageSchema, authorEntityId, generateArticleAuthorNode, generateAuthorPersonSchema, generateAuthorSchema, resolveArticleArtwork, resolveRepresentativeImage, withArticleAuthorIdentity };
@@ -1,5 +1,5 @@
1
1
  import { secretsMatch, readCappedBody } from './chunk-XTRZGXB2.js';
2
- import { SITE_KIT_VERSION } from './chunk-C2UROFGZ.js';
2
+ import { SITE_KIT_VERSION } from './chunk-5S7YTOCB.js';
3
3
 
4
4
  // src/llms/revalidate.ts
5
5
  function createLlmsRevalidateHandler(secret, options) {
@@ -1,4 +1,4 @@
1
- import { publishSiteCredential } from './chunk-U7Y3QG3A.js';
1
+ import { publishSiteCredential } from './chunk-MZX4EN6Z.js';
2
2
 
3
3
  // src/client/SiteKitCredential.tsx
4
4
  function SiteKitCredential({
@@ -1,4 +1,4 @@
1
- import { FormEnhancer } from './chunk-5RYSTKVU.js';
1
+ import { FormEnhancer } from './chunk-LEGMPX6N.js';
2
2
  import { StaticForm } from './chunk-LNSF6BJC.js';
3
3
  import { serverApiFetch } from './chunk-C7TLGL3R.js';
4
4
  import { jsx } from 'react/jsx-runtime';
@@ -1,6 +1,6 @@
1
1
  'use client';
2
- export { SiteKitCredential } from '../chunk-IEGDPRQB.js';
3
- import '../chunk-U7Y3QG3A.js';
2
+ export { SiteKitCredential } from '../chunk-XI2XM7UG.js';
3
+ import '../chunk-MZX4EN6Z.js';
4
4
  export { VISUAL_VIEWPORT_GAP_PROPERTY, VisualViewportGap, useVisualViewportGap } from '../chunk-6T6CQVNL.js';
5
5
  export { useDeferredActivation } from '../chunk-YC7ELZS3.js';
6
6
  import '../chunk-FESKKJAS.js';
@@ -9,6 +9,6 @@ export { trackConversion, trackEvent } from '../chunk-LCDVPAMR.js';
9
9
  export { getOrCreateSessionId, getOrCreateVisitorId, useSiteKitIdentity } from '../chunk-ZKADJNR2.js';
10
10
  import '../chunk-5TM3WPCI.js';
11
11
  import '../chunk-GJWI74ZZ.js';
12
- import '../chunk-C4AFNZT2.js';
13
- import '../chunk-C2UROFGZ.js';
12
+ import '../chunk-RBNHRZGD.js';
13
+ import '../chunk-5S7YTOCB.js';
14
14
  import '../chunk-PKBMQBKP.js';
@@ -1,11 +1,11 @@
1
1
  'use client';
2
- export { FLEET_HEARTBEAT_PATH, FleetHeartbeat, detectNextVersion, sendFleetHeartbeat } from '../chunk-LPKKSYKX.js';
2
+ export { FLEET_HEARTBEAT_PATH, FleetHeartbeat, detectNextVersion, sendFleetHeartbeat } from '../chunk-5TDZHIG6.js';
3
3
  export { FLEET_CONTRACT_VERSION, FLEET_MODULES, FLEET_MODULES_MAX, FLEET_SITE_MAX_LENGTH, FLEET_VERSION_MAX_LENGTH, FLEET_VERSION_PATTERN, buildFleetHeartbeatPayload, isFleetModule, normalizeFleetModules, normalizeFleetSite, sanitizeFleetVersion } from '../chunk-ZTZQPYP6.js';
4
4
  import '../chunk-YC7ELZS3.js';
5
- import '../chunk-JO76F5WP.js';
5
+ import '../chunk-DLM4FFNH.js';
6
6
  import '../chunk-EKBEOXTH.js';
7
- import '../chunk-OIWFXUVD.js';
8
- import '../chunk-C4AFNZT2.js';
7
+ import '../chunk-3VSFOD2Z.js';
8
+ import '../chunk-RBNHRZGD.js';
9
9
  import '../chunk-W7OPEMGJ.js';
10
- import '../chunk-C2UROFGZ.js';
10
+ import '../chunk-5S7YTOCB.js';
11
11
  import '../chunk-PKBMQBKP.js';
@@ -1,13 +1,13 @@
1
1
  'use client';
2
- export { configureFormsApi, defineForm, field, formsApi, initializeForms } from '../chunk-X4IJS7LV.js';
3
- export { FormClient, ManagedForm } from '../chunk-XLAXYPTK.js';
4
- import '../chunk-DPSNMOMZ.js';
2
+ export { configureFormsApi, defineForm, field, formsApi, initializeForms } from '../chunk-BLCHQIWR.js';
3
+ export { FormClient, ManagedForm } from '../chunk-J4KX2D3Q.js';
4
+ import '../chunk-44GVU5UH.js';
5
5
  export { DatePicker } from '../chunk-SOD2F6C2.js';
6
6
  export { FormCelebration } from '../chunk-YGGDU6C3.js';
7
- export { useForm, useFormTracking } from '../chunk-MQ4REMTH.js';
7
+ export { useForm, useFormTracking } from '../chunk-JV5YWTJN.js';
8
8
  import '../chunk-Q6E4OPGG.js';
9
9
  import '../chunk-5ROTTMBL.js';
10
- export { FormEnhancer } from '../chunk-5RYSTKVU.js';
10
+ export { FormEnhancer } from '../chunk-LEGMPX6N.js';
11
11
  import '../chunk-C2FZBUSS.js';
12
12
  import '../chunk-YC7ELZS3.js';
13
13
  import '../chunk-DXO6SBMD.js';
@@ -18,8 +18,8 @@ import '../chunk-NJCTQH2P.js';
18
18
  import '../chunk-L2V5PUFN.js';
19
19
  import '../chunk-5TM3WPCI.js';
20
20
  import '../chunk-GJWI74ZZ.js';
21
- import '../chunk-OIWFXUVD.js';
22
- import '../chunk-C4AFNZT2.js';
21
+ import '../chunk-3VSFOD2Z.js';
22
+ import '../chunk-RBNHRZGD.js';
23
23
  import '../chunk-W7OPEMGJ.js';
24
- import '../chunk-C2UROFGZ.js';
24
+ import '../chunk-5S7YTOCB.js';
25
25
  import '../chunk-PKBMQBKP.js';
@@ -1,5 +1,5 @@
1
- export { ServerForm, getFormConfig } from '../chunk-NA4Z4CQG.js';
2
- import '../chunk-5RYSTKVU.js';
1
+ export { ServerForm, getFormConfig } from '../chunk-YM5KMNTU.js';
2
+ import '../chunk-LEGMPX6N.js';
3
3
  import '../chunk-C2FZBUSS.js';
4
4
  import '../chunk-YC7ELZS3.js';
5
5
  export { StaticForm } from '../chunk-LNSF6BJC.js';
@@ -167,6 +167,11 @@ export type FormSubmitOutcome = {
167
167
  totalSteps: number;
168
168
  };
169
169
  export interface FormSubmitResult {
170
+ /** Receipt only; never exposes existing subscriber membership to the public. */
171
+ publicationEnrollment?: {
172
+ status: 'accepted';
173
+ message: string;
174
+ };
170
175
  success: boolean;
171
176
  /** The form_submissions row id (same as submissionId). */
172
177
  id: string;
@@ -120,6 +120,10 @@ export interface UseFormReturn {
120
120
  /** Submission state */
121
121
  isSubmitting: boolean;
122
122
  isComplete: boolean;
123
+ /** Accepted server receipt, distinct from confirmed newsletter enrollment. */
124
+ submissionResult: FormSubmitResult | null;
125
+ /** Retry loading configuration, preserving the visitor's entered values. */
126
+ reload: () => void;
123
127
  /** Reset form to initial state */
124
128
  reset: () => void;
125
129
  /**
@@ -1,6 +1,6 @@
1
- export { ManagedImage, assignImageToSlot, clearImageSlot, fetchManagedImage, fetchManagedImages, listImageFiles, uploadImage } from '../chunk-3OY52UFM.js';
1
+ export { ManagedImage, assignImageToSlot, clearImageSlot, fetchManagedImage, fetchManagedImages, listImageFiles, uploadImage } from '../chunk-OKITZFML.js';
2
2
  export { ManagedFavicon, getManagedFaviconUrl } from '../chunk-GW7GSBO7.js';
3
- import '../chunk-OIWFXUVD.js';
4
- import '../chunk-C4AFNZT2.js';
5
- import '../chunk-C2UROFGZ.js';
3
+ import '../chunk-3VSFOD2Z.js';
4
+ import '../chunk-RBNHRZGD.js';
5
+ import '../chunk-5S7YTOCB.js';
6
6
  import '../chunk-PKBMQBKP.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { SITE_KIT_VERSION } from './chunk-C2UROFGZ.js';
1
+ export { SITE_KIT_VERSION } from './chunk-5S7YTOCB.js';
2
2
  import './chunk-PKBMQBKP.js';