@sonordev/site-kit 7.1.2 → 7.3.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 (176) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +60 -0
  3. package/README.md +9 -8
  4. package/agent-manifest.json +17 -7
  5. package/dist/{AnalyticsProvider-ZB33W6MC.js → AnalyticsProvider-DJQA66AG.js} +4 -4
  6. package/dist/{ArticleViewTracker-GMHYLLLZ.js → ArticleViewTracker-GFOAXFV7.js} +3 -3
  7. package/dist/{BlocksPopup-EA6LYL25.js → BlocksPopup-AHDU34TL.js} +5 -5
  8. package/dist/ChatWidget-FHBAHFZX.js +17 -0
  9. package/dist/{FileField-QKA3JB2Q.js → FileField-J5W5MIAZ.js} +3 -3
  10. package/dist/{FormSpotlight-AQTIA5TP.js → FormSpotlight-PE5YZBFZ.js} +20 -6
  11. package/dist/{FormStage-FDTLTP3B.js → FormStage-OOPQC27J.js} +27 -7
  12. package/dist/ManagedForm-JRZAJ7SZ.js +16 -0
  13. package/dist/{ManagedNewsletterForm-J6QSAGGP.js → ManagedNewsletterForm-TC5542ZG.js} +7 -5
  14. package/dist/{SignalCore-JZNXOLNT.js → SignalCore-2YP5FCAS.js} +3 -3
  15. package/dist/SiteChat-EGEZJJVV.js +5 -0
  16. package/dist/{SiteDesignReporter-W4EJUSTE.js → SiteDesignReporter-YEEWJKWN.js} +5 -5
  17. package/dist/SitePopups-LQBIXIFV.js +10 -0
  18. package/dist/SitemapSync-LSZ2EYOF.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-HF57LG73.js → chunk-2AD3CDRR.js} +43 -29
  39. package/dist/{chunk-M3YEPKP7.js → chunk-2ECWEUHP.js} +1 -1
  40. package/dist/{chunk-LUXVWITO.js → chunk-4NTBQNHA.js} +247 -78
  41. package/dist/{chunk-AH5Q262S.js → chunk-56KNHB4U.js} +1 -1
  42. package/dist/{chunk-GLHQ3LRO.js → chunk-6U3VLV2C.js} +1 -1
  43. package/dist/{chunk-3SDOQUUF.js → chunk-BEBR4OJD.js} +2 -2
  44. package/dist/{chunk-JSZN6LDZ.js → chunk-BF7TZYC3.js} +38 -31
  45. package/dist/{chunk-AQSNPZY4.js → chunk-C2FZBUSS.js} +80 -1
  46. package/dist/{chunk-PHYMNUJU.js → chunk-CYUFBKJQ.js} +1 -1
  47. package/dist/{chunk-6WIQCXOO.js → chunk-DNPNYWVL.js} +1 -1
  48. package/dist/{chunk-V6FDQLR7.js → chunk-EPT6FJUY.js} +53 -21
  49. package/dist/chunk-HODO5BX5.js +28 -0
  50. package/dist/chunk-HW7B43E5.js +14 -0
  51. package/dist/{chunk-PGMVL4AB.js → chunk-I2YX3HVD.js} +2 -2
  52. package/dist/{chunk-JTU2GOM4.js → chunk-L7U23XI2.js} +18 -6
  53. package/dist/{chunk-YEMPUQVU.js → chunk-MKMD2HJW.js} +2 -2
  54. package/dist/{chunk-Q4W23JYM.js → chunk-MRQ2U4LZ.js} +1 -1
  55. package/dist/chunk-MVR4FY5R.js +187 -0
  56. package/dist/{chunk-VGH4MDTU.js → chunk-NI2XNEMR.js} +1 -1
  57. package/dist/{chunk-4Y5FWDPM.js → chunk-NZ4RXN3G.js} +55 -7
  58. package/dist/{chunk-RHNWA34F.js → chunk-O5RGXTWU.js} +41 -7
  59. package/dist/{chunk-TXBAOEKG.js → chunk-PKGN32AU.js} +5 -4
  60. package/dist/chunk-Q6E4OPGG.js +40 -0
  61. package/dist/chunk-RDPW4D33.js +45 -0
  62. package/dist/{chunk-UXOGCKL6.js → chunk-T4DLETKR.js} +1 -1
  63. package/dist/{chunk-NK4CSJNP.js → chunk-UPRSH6Q4.js} +2 -2
  64. package/dist/{chunk-ERXXITG4.js → chunk-VF4F5WXZ.js} +1 -1
  65. package/dist/chunk-VWWITFWM.js +300 -0
  66. package/dist/{chunk-WWSHFRDL.js → chunk-WDQB4OXY.js} +7 -3
  67. package/dist/{chunk-VDF5DFWT.js → chunk-WGG6GTKW.js} +1 -1
  68. package/dist/{chunk-CSTQDNGK.js → chunk-WGIDC6QP.js} +43 -24
  69. package/dist/{chunk-V3PUVBME.js → chunk-XLSQTTUE.js} +2 -4
  70. package/dist/{chunk-JBE46QJZ.js → chunk-Y6LP36UW.js} +1 -1
  71. package/dist/chunk-YJLS6JBI.js +370 -0
  72. package/dist/{chunk-QEDHRBAT.js → chunk-Z65DHMTW.js} +26 -9
  73. package/dist/{chunk-ILU5I6RW.js → chunk-ZHS2XFLV.js} +1 -1
  74. package/dist/{chunk-D5EC5KVO.js → chunk-ZKHI4E2B.js} +2 -2
  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/agent-access.d.ts +4 -0
  112. package/dist/llms/contract.js +1 -1
  113. package/dist/llms/index.js +6 -6
  114. package/dist/maps/index.js +3 -3
  115. package/dist/mcp/WebMcpTools.d.ts +5 -4
  116. package/dist/mcp/client.js +14 -7
  117. package/dist/mcp/discovery.d.ts +48 -0
  118. package/dist/mcp/handlers.d.ts +13 -7
  119. package/dist/mcp/index.d.ts +5 -3
  120. package/dist/mcp/index.js +62 -30
  121. package/dist/mcp/serverCard.d.ts +6 -7
  122. package/dist/mcp/sonor.js +14 -11
  123. package/dist/revalidate/index.js +2 -2
  124. package/dist/seo/client.js +4 -4
  125. package/dist/seo/index.js +15 -8
  126. package/dist/seo/llms/contract.js +1 -1
  127. package/dist/seo/llms.js +6 -6
  128. package/dist/seo/register-sitemap-cli.js +1 -1
  129. package/dist/seo/sitemap.js +4 -4
  130. package/dist/server/index.js +2 -2
  131. package/dist/shared/dialog.d.ts +27 -0
  132. package/dist/shared/identity.d.ts +1 -1
  133. package/dist/shared/layers.d.ts +7 -0
  134. package/dist/shared/mid-form.d.ts +53 -0
  135. package/dist/shared/reporting-gate.d.ts +1 -1
  136. package/dist/shared/version.d.ts +1 -1
  137. package/dist/shared/visual-viewport-gap.d.ts +1 -1
  138. package/dist/signal/index.js +2 -2
  139. package/dist/signal/types.d.ts +1 -1
  140. package/dist/sitemap/index.js +4 -4
  141. package/dist/{socket-loader-R24ZSRSQ.js → socket-loader-CGIPEG74.js} +1 -1
  142. package/dist/sync/index.js +5 -5
  143. package/dist/types.d.ts +3 -1
  144. package/dist/{web-vitals.attribution-GD6LGLVF.js → web-vitals.attribution-N2PCCF4Q.js} +1 -1
  145. package/dist/website/BlocksPopup.d.ts +1 -1
  146. package/dist/website/PopupBlocks.d.ts +5 -2
  147. package/dist/website/SitePopups.d.ts +25 -0
  148. package/dist/website/images.js +4 -4
  149. package/dist/website/index.js +6 -6
  150. package/dist/website/popup-rules.d.ts +46 -0
  151. package/dist/website/popup-types.d.ts +113 -0
  152. package/dist/website/popups.d.ts +2 -6
  153. package/dist/website/popups.js +6 -14
  154. package/dist/{writeLLMsTxt-G3JBNBC5.js → writeLLMsTxt-2NQVDGYI.js} +3 -3
  155. package/docs/MIGRATING-TO-7.md +41 -9
  156. package/docs.json +2 -1
  157. package/package.json +2 -2
  158. package/src/analytics/README.md +2 -2
  159. package/src/articles/README.md +4 -1
  160. package/src/{engage → chat}/README.md +63 -62
  161. package/src/forms/README.md +49 -3
  162. package/src/layout/README.md +8 -5
  163. package/src/llms/README.md +69 -0
  164. package/src/mcp/README.md +50 -16
  165. package/src/seo/README.md +3 -1
  166. package/src/sync/README.md +10 -0
  167. package/src/website/README.md +133 -0
  168. package/dist/ChatWidget-UCDWMWCW.js +0 -15
  169. package/dist/EngageWidget-7ZYSK4GE.js +0 -11
  170. package/dist/ManagedForm-H7BVAH2W.js +0 -14
  171. package/dist/SitemapSync-XBANGKHZ.js +0 -8
  172. package/dist/chunk-NDF4A5JM.js +0 -37
  173. package/dist/chunk-VLARKWZU.js +0 -100
  174. package/dist/chunk-YCJT4JJG.js +0 -837
  175. package/dist/engage/DesignRenderer.d.ts +0 -57
  176. package/dist/engage/element-rules.d.ts +0 -38
@@ -0,0 +1,183 @@
1
+ /**
2
+ * @sonordev/contracts/error-page: telling an error page from a page (v1)
3
+ *
4
+ * Three questions every writer of page content and page copy has to answer the
5
+ * same way, so they live here once:
6
+ *
7
+ * 1. Is this stored text a rendered ERROR PAGE rather than the page's own
8
+ * content? (`looksLikeErrorPage`, `usablePageText`)
9
+ * 2. Does this generated COPY say the page itself is missing or unavailable?
10
+ * (`findErrorPageClaim`, `describesErrorPage`)
11
+ * 3. Does any string in a page's managed copy (a title, a keyword list, a
12
+ * JSON-LD block) say so? (`findPageCopyClaim`: question 2 over every
13
+ * string of a record, naming the field that holds it)
14
+ *
15
+ * Why it exists: a page's stored text is the text a visitor's browser (or a
16
+ * build's crawler) rendered. Visit a URL while it's briefly broken and the
17
+ * error page's words are stored as that page's content. Everything generated
18
+ * from that content then describes a working page as broken, and the result
19
+ * is published as the page's title, meta description, social tags, JSON-LD
20
+ * and AI-crawler notes. The outage ends in a deploy; the poisoned copy
21
+ * doesn't, because nothing downstream asks whether its input was ever the
22
+ * page.
23
+ *
24
+ * How the text check works
25
+ *
26
+ * - It reads the START of the text (300 characters), lowercased, with
27
+ * whitespace collapsed, HTML entities decoded and curly quotes made
28
+ * straight. Error copy leads; articles bury it.
29
+ * - The signatures are generic on purpose. Every site words its 404
30
+ * differently and frameworks ship their own ("This page could not be
31
+ * found.", "Application error: a client-side exception has occurred"), so
32
+ * the shape is what's matched, not any one site's copy. They're grouped by
33
+ * family below: not found, server and client errors, unavailable and
34
+ * maintenance, walls.
35
+ * - Stored text is often the page's elements run together with no separator
36
+ * ("404This page could not be found."), so no signature depends on a word
37
+ * boundary around the text it's matching.
38
+ * - A signature that follows a word that DESCRIBES errors rather than
39
+ * showing one ("if something went wrong", "how to fix a 404 error", a
40
+ * quote mark) doesn't count: a help page that names an error isn't one.
41
+ * Navigation is Title Case and prose isn't, so a describing word counts
42
+ * only as it's written in lower case, with only its first letter
43
+ * capitalized ("How to fix..."), or opening the text or a sentence;
44
+ * "Home Blog How To Something went wrong" is still an error page, and the
45
+ * words a menu uses too ("About", "Monitoring", "Alerts for") describe an
46
+ * error only in lower case. A describing word inside the matched phrase
47
+ * counts too, and after a described match the search resumes one character
48
+ * on, so a real error later in the same stretch is still found.
49
+ * - A not-found phrase that sits in a question ("Page not found? Use our site
50
+ * map", "Product not found in your size? Ask us") is a help line, and so is
51
+ * most copy that merely uses the words ("Rare parts can't be found
52
+ * elsewhere": the subject has to be a page, URL, link, file, site or app).
53
+ * - Up to ERROR_PAGE_MAX_WORDS words every check applies, but the loosest
54
+ * ones apply only to the shortest texts: a 404 glued to its copy and
55
+ * "refresh the page and try again" up to 60 words, a dynamic route's own
56
+ * "<name> not found" state up to 40. From there up to 400 words only the
57
+ * prefix-aware check applies: a text written as "<meta description> —
58
+ * <every string on the page>" carries its header and footer strings along,
59
+ * which pushes a rendered error state past the short-text ceiling, so the
60
+ * check looks at how the text opens after the description and a skip link,
61
+ * and an opener followed by "explained" or "how to" is an article. Beyond
62
+ * 400 words the text is prose.
63
+ *
64
+ * `wordCount` semantics: pass the word count of `text` itself. Pass the whole
65
+ * document's count only when `text` is a truncated excerpt of it. A supplied
66
+ * count can only RAISE the count the check uses (an excerpt of a long document
67
+ * is prose however short it is), never lower it: text that is longer than the
68
+ * count you gave is counted by what it holds. A browser's page-wide count
69
+ * (navigation and footer included) is therefore the wrong number for a short
70
+ * error state it scraped: pass the count of the text you pass.
71
+ *
72
+ * How the copy check works
73
+ *
74
+ * `findErrorPageClaim` looks for the copy saying THE PAGE is missing or
75
+ * unavailable ("Page Not Found | Acme", "This URL currently returns a 404",
76
+ * "This listing has been removed", "The page failed to load"). The subject
77
+ * has to be the page, its URL, link or listing, so copy about something
78
+ * unavailable on a working page ("sunset cruises are not available at this
79
+ * time") is left alone, and so is a qualified one ("unavailable in winter").
80
+ * Whitespace is collapsed and each string is read up to 5,000 characters, so
81
+ * hostile whitespace costs nothing. The page's own text exempts a phrase the
82
+ * page really says; a page that is itself an error page exempts nothing.
83
+ *
84
+ * `findPageCopyClaim(fields, pageText?)` is the same check over a record:
85
+ * each value of `fields` is checked if it's a string, and walked for every
86
+ * string it holds if it's an array or an object (8 levels deep, 2,000
87
+ * leaves at most; numbers, booleans and null are ignored). It returns the
88
+ * first claim as `{ field, phrase }`, `field` being the TOP-LEVEL key (the
89
+ * column or property that holds the copy), or null. Use it for a page row's
90
+ * managed title, description, keywords and JSON-LD in one call.
91
+ *
92
+ * Known limits (a text-only check can't close these)
93
+ *
94
+ * - Any error phrase in the first 300 characters of a text of 120 words or
95
+ * fewer reads as an error page, wherever in those 300 characters it sits, unless
96
+ * something describes it (see above): a short page ABOUT errors ("Page not
97
+ * found: how to design a helpful 404 page. Five examples.", or a web
98
+ * agency's line about its "custom 404 error page") can be misread. Texts of
99
+ * 121 to 400 words are only read for how they open, and longer ones are
100
+ * prose. The loosest wordings (a 404 glued to its copy, "refresh the page
101
+ * and try again", "<name> not found") apply only up to 60, 60 and 40 words.
102
+ * - A business whose name is an error phrase ("404 Brewing Co.", "Page Not
103
+ * Found Records") reads as an error page when its name leads the text.
104
+ * - Chrome in front hides the error state from the wordings anchored to the
105
+ * start of the text ("Not Found", "Be right back.", "Error: ...", "Oops!
106
+ * This page...", a permission wall, a dynamic route's "<name> not found").
107
+ * The wordings that hold anywhere in the first 300 characters (a status with
108
+ * its reason, "could not be found", "something went wrong") are what's
109
+ * left. Prefer the page's main content.
110
+ * - The text alone never proves a page failed. Where a call site observed
111
+ * the HTTP status (a browser reports it), require a status of 400 or more
112
+ * as well, and treat this check as the fallback for the sites and the
113
+ * paths that report none.
114
+ * - It reads English. A 404 in another language isn't matched.
115
+ * - Walls and pages served by the host in front of the site (a CDN block, a
116
+ * bot challenge, a parked domain, a default server page, a browser
117
+ * interstitial) never reach the writers this guards, so they aren't
118
+ * matched here.
119
+ * - Copy that claims the page is missing in words outside the closed list
120
+ * below isn't caught; the real protection is that error text never reaches
121
+ * the generator (`usablePageText`).
122
+ *
123
+ * Pure string logic: no I/O, no Node built-ins, no lookbehind (this ships to
124
+ * browsers, and Safari before 16.4 throws on a lookbehind at parse time).
125
+ */
126
+ /** Bump on breaking changes to what counts as an error page or an error claim. */
127
+ export declare const ERROR_PAGE_CONTRACT_VERSION: 1;
128
+ /**
129
+ * Above this many words, text is real prose even if it mentions a 404: only
130
+ * the prefix-aware check (up to 400 words, see the header) still looks at it.
131
+ *
132
+ * The gate is what makes the check safe: an article about broken links ("How
133
+ * to fix 404 errors on your site") legitimately holds every phrase below and
134
+ * must not be discarded. A rendered error page is short, and this ceiling
135
+ * still separates the two by an order of magnitude.
136
+ */
137
+ export declare const ERROR_PAGE_MAX_WORDS = 120;
138
+ /**
139
+ * True when this text is a rendered error page rather than page content.
140
+ *
141
+ * @param text the scraped or extracted body text
142
+ * @param wordCount the word count of `text` itself, or the whole document's
143
+ * when `text` is a truncated excerpt of it. It can only raise
144
+ * the count the check uses, never lower it; derived from
145
+ * `text` when absent.
146
+ */
147
+ export declare function looksLikeErrorPage(text?: string | null, wordCount?: number | null): boolean;
148
+ /**
149
+ * The page's stored text, or null when it's an error page. What a model may
150
+ * be shown as "the page's content".
151
+ */
152
+ export declare function usablePageText(text?: string | null, wordCount?: number | null): string | null;
153
+ /**
154
+ * The phrase in `copy` that says the page itself is missing or unavailable, or
155
+ * null when the copy is fine.
156
+ *
157
+ * @param pageText the page's own text. A phrase the page really says (an
158
+ * article about fixing "page not found" errors) is allowed;
159
+ * only copy that claims what the page doesn't is a finding.
160
+ * A pageText that is itself an error page allows nothing: its
161
+ * words are the poison, so the copy that repeats them would
162
+ * otherwise vouch for itself.
163
+ */
164
+ export declare function findErrorPageClaim(copy?: string | null, pageText?: string | null): string | null;
165
+ /** True when `copy` says the page itself is missing or unavailable (see `findErrorPageClaim`). */
166
+ export declare function describesErrorPage(copy?: string | null, pageText?: string | null): boolean;
167
+ /**
168
+ * The first string anywhere in a record of managed copy that says the page
169
+ * itself is missing or unavailable, or null. `findErrorPageClaim` over every
170
+ * string of `fields`, and the record's own field name with the phrase.
171
+ *
172
+ * Each value is checked if it's a string, and walked for the strings it holds
173
+ * if it's an array or an object (a JSON-LD block, a keyword list), up to 8
174
+ * levels deep, 2,000 strings and 100,000 characters in all; numbers, booleans and
175
+ * null are ignored (and don't count toward those limits).
176
+ * `field` is the TOP-LEVEL key, the column or property that holds the copy.
177
+ * `pageText` exempts what the page really says, as in `findErrorPageClaim`; the
178
+ * page's own text is read once for the whole record.
179
+ */
180
+ export declare function findPageCopyClaim(fields: object, pageText?: string | null): {
181
+ field: string;
182
+ phrase: string;
183
+ } | null;
@@ -28,6 +28,7 @@ export interface LLMsPayloadMeta {
28
28
  /** Optional single-line, public-safe note in the llms.txt blockquote (e.g. scope / not legal advice). */
29
29
  llms_disclaimer?: string | null;
30
30
  }
31
+ export declare function clipAtWordBoundary(text: string, max: number): string;
31
32
  /**
32
33
  * Public-safe summary for llms.txt `[title](url): notes` — never CRM-only or internal copy.
33
34
  */
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @sonordev/contracts/proposal-sitemap — how a proposal's site plan is counted (v1)
3
+ *
4
+ * Single source of truth for the numbers a website proposal's site plan
5
+ * (the `SitemapPlan` block) states: how many pages the build has, when the
6
+ * "before → after" comparison may show, which custom labels the block may
7
+ * carry, and whether page counts written elsewhere in the proposal agree with
8
+ * the plan. The proposal generator stamps them, the Sonor API checks them and
9
+ * the dashboard renders them, so the headline, the plan and the price can't
10
+ * each state a different number.
11
+ *
12
+ * Why it exists: the numbers in a plan are what a buyer reads first, and a
13
+ * count written by a language model can't be trusted to add up. A rebuild
14
+ * that keeps every page must never read as a loss ("24 → 0 pages"), one
15
+ * address listed twice must not count twice, and the headline must not state
16
+ * a number the plan doesn't. Counts are computed here, never taken from prose.
17
+ */
18
+ /** Bump on breaking changes to how a plan is counted. */
19
+ export declare const PROPOSAL_SITEMAP_CONTRACT_VERSION: 1;
20
+ /**
21
+ * A count the plan states: a non-negative whole number, as a number or a
22
+ * numeric string. Anything else is no count at all (undefined), never 0.
23
+ */
24
+ export declare function sitemapCount(v: unknown): number | undefined;
25
+ /**
26
+ * The address a plan row stands for, in one shape: lowercase, no scheme or
27
+ * host, no query or fragment, a leading slash and no trailing one ("/" for the
28
+ * home page). Undefined for a row without a slug; such a row always counts,
29
+ * since nothing proves it repeats another.
30
+ */
31
+ export declare function sitemapPageKey(slug: unknown): string | undefined;
32
+ export interface SitemapPlanCounts {
33
+ /** Core pages (Home, About, Contact and the like), each address once. */
34
+ core: number;
35
+ /**
36
+ * The plan's own pages: top-level pages plus the pages under them, minus
37
+ * any address already counted as a core page.
38
+ */
39
+ architecture: number;
40
+ /** Existing articles re-published with the build (the plan's `blog.count`). */
41
+ articles: number;
42
+ /** Everything the client gets: core + architecture + articles. */
43
+ full: number;
44
+ /**
45
+ * Pages that exist today and carry over (status rebuild, optimize or
46
+ * migrate, plus the re-published articles).
47
+ */
48
+ existing: number;
49
+ /** Pages the build adds (status new, or no status). */
50
+ added: number;
51
+ }
52
+ /**
53
+ * Count a site plan's pages, each address once. Core pages are counted
54
+ * first, then each top-level page and the pages under it; a row whose
55
+ * address was already counted is skipped. Rows without a slug always count.
56
+ */
57
+ export declare function countSitemapPlan(props: unknown): SitemapPlanCounts;
58
+ export interface SitemapTransformationCounts {
59
+ /** Today's count, as the plan states it. */
60
+ before: number;
61
+ /** The build's count. */
62
+ after: number;
63
+ }
64
+ /**
65
+ * The before → after comparison a plan may show, or null when it must not
66
+ * show. It shows only when the plan states today's count and the build is
67
+ * bigger. A missing after-count falls back to the plan's own pages (then its
68
+ * `totalPages`); an after-count of zero, or one no bigger than today's, hides
69
+ * the comparison. A rebuild that keeps the same pages isn't a before and after.
70
+ */
71
+ export declare function sitemapTransformation(props: unknown): SitemapTransformationCounts | null;
72
+ /**
73
+ * Words a plan uses for its own pages, for a business whose pages aren't
74
+ * services sold to industries (communities and towns, practice areas,
75
+ * locations). Each replaces one default: `architecture` the heading
76
+ * ("Service Architecture"), `pillar` a top-level page ("Service pillar"),
77
+ * `child` a page under it ("Industry page"). Singular, title case.
78
+ */
79
+ export interface SitemapPlanLabels {
80
+ architecture?: string;
81
+ pillar?: string;
82
+ child?: string;
83
+ }
84
+ /** The longest a label may be. */
85
+ export declare const SITEMAP_LABEL_MAX = 40;
86
+ /**
87
+ * Keep only usable labels: strings of 1 to {@link SITEMAP_LABEL_MAX}
88
+ * characters on one line, whitespace collapsed, no angle brackets. Undefined
89
+ * when none survive, so a plan without labels reads the defaults.
90
+ */
91
+ export declare function normalizeSitemapLabels(raw: unknown): SitemapPlanLabels | undefined;
92
+ export type PageCountUnit = 'page' | 'url';
93
+ export interface PageCountClaim {
94
+ /** The number stated. */
95
+ value: number;
96
+ /** Whether it counts pages or URLs. */
97
+ unit: PageCountUnit;
98
+ /** The words that make the claim, e.g. "27 existing URLs". */
99
+ text: string;
100
+ /** Where the claim starts in the text. */
101
+ index: number;
102
+ }
103
+ /**
104
+ * Every page or URL count stated in a piece of text. A number that's part of
105
+ * a name ("Troy 7 Apartments get pages of their own") isn't a count.
106
+ */
107
+ export declare function findPageCountClaims(text: unknown): PageCountClaim[];
108
+ export interface PageCountIssue {
109
+ /** The block the claim is in, e.g. "GlassHero". */
110
+ section: string;
111
+ /** Where in the block, e.g. "stats[0]" or "tiers[0].features[1]". */
112
+ field: string;
113
+ /** The number stated. */
114
+ claimed: number;
115
+ unit: PageCountUnit;
116
+ /** The words that make the claim. */
117
+ text: string;
118
+ /** The page count the plan adds up to. */
119
+ planned: number;
120
+ }
121
+ /**
122
+ * Page and URL counts a proposal states that match nothing its site plan or
123
+ * its measured evidence adds up to. A stated count passes when it equals any
124
+ * of: the plan's full, core, own, article, carried-over or added page count;
125
+ * the plan's stated before-count; or the measured URL and page counts the
126
+ * evidence blocks carry. Empty when the proposal has no site plan.
127
+ */
128
+ export declare function proposalPageCountIssues(sections: unknown): PageCountIssue[];
129
+ /** One plain sentence for a person reviewing the proposal before it's sent. */
130
+ export declare function describePageCountIssue(issue: PageCountIssue): string;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * JSON-LD template placeholders: found once, dropped wherever sonor-api serves
3
+ * stored schema to a site, and never stored as implemented by register-schema.
4
+ *
5
+ * Stored JSON-LD (seo_schema_markup.schema_json, seo_pages.managed_schema) can
6
+ * carry a template's unfilled slots. Schema extracted from a site's source,
7
+ * where the base URL was a variable, comes back as https://example.com; an AI
8
+ * filling a template leaves "Example", "+1-000-000-0000", "[Resident Name]",
9
+ * "{plan.name}", or an object that is only a "note" saying what goes there.
10
+ * site-kit renders all of it on the live page, where search engines and AI
11
+ * crawlers take it as the business's identity. Observed 2026-10-01: a live
12
+ * site served an Organization named "Example", phone +1-000-000-0000, on its
13
+ * home page, and dozens more of its implemented rows carried example.com URLs
14
+ * and unfilled template slots.
15
+ *
16
+ * Conservative on purpose, because a false positive removes real schema from a
17
+ * live site:
18
+ * - Domains: only the reserved example domains (example.com, example.net,
19
+ * example.org, their subdomains, and the .example TLD), and only when the
20
+ * whole value is a URL, host or email address. A real domain with
21
+ * "example" in its path, or prose that mentions example.com, is fine.
22
+ * - Names: whole-value matches only (trimmed, case-insensitive) on name-like
23
+ * properties. "Example Plumbing Co" is a real name.
24
+ * - Phones (telephone, faxNumber, tel: links): all zeros, 123-456-7890,
25
+ * XXX-XXX-XXXX, and 555-0100 through 555-0199, the North American range
26
+ * reserved for fiction. 555-0200 and 555-1212 are real numbers.
27
+ * - Template slots: a value that is wholly a bracketed slot ("[YYYY-MM-DD]")
28
+ * or a JSX expression ("{plan.name}"), a {{mustache}} slot, a
29
+ * REPLACE_WITH_ marker, a title-case field name in brackets inside text
30
+ * ("Call [Phone Number]"), or a JSX member expression inside a URL. A URI
31
+ * template's {search_term_string} is how schema.org spells a SearchAction
32
+ * and is never a finding.
33
+ * - Annotations: an object that holds nothing but an AI note. A note key on
34
+ * a real node is stripped (it isn't schema.org vocabulary) and the node
35
+ * stays.
36
+ *
37
+ * The unit is the NODE: the innermost object with @type or @id (or a top-level
38
+ * or @graph member) whose own values hold the placeholder. A placeholder node
39
+ * is dropped whole, since its other values came from the same unfilled
40
+ * template. Its real siblings, and a real parent it hangs off, stay: an
41
+ * FAQPage keeps its questions when only its publisher was a placeholder. A
42
+ * node left with nothing but JSON-LD keywords once its placeholder children
43
+ * are gone (a BreadcrumbList whose every item was a placeholder) goes too, so
44
+ * the site falls back to what it generates itself.
45
+ *
46
+ * Single source of truth for "is this stored schema a placeholder", published
47
+ * as `@sonordev/contracts/schema-placeholders`: sonor-api's public schema reads
48
+ * and register-schema, and site-kit's ManagedSchema and LLMSchema (so a site
49
+ * drops a placeholder whichever Sonor source served it) all call it. Add new
50
+ * shapes here, with a test, never in a caller.
51
+ */
52
+ export type SchemaPlaceholderReason = 'example_domain' | 'placeholder_phone' | 'placeholder_name' | 'template_token' | 'annotation' | 'emptied';
53
+ export interface SchemaPlaceholderEvidence {
54
+ reason: SchemaPlaceholderReason;
55
+ /** The property that held the value. */
56
+ key: string;
57
+ /** The value, cut short for logs. */
58
+ value: string;
59
+ }
60
+ export interface SchemaPlaceholderNode {
61
+ /** Where the node sits: '$' for the root, then .key and [index] steps. */
62
+ path: string;
63
+ /** The node's @type ('A,B' when it has several), or null. */
64
+ type: string | null;
65
+ reasons: SchemaPlaceholderReason[];
66
+ evidence: SchemaPlaceholderEvidence[];
67
+ }
68
+ export interface SchemaPlaceholderScan<T> {
69
+ /** The value without its placeholder nodes: the same reference when nothing was found, null when nothing real is left. */
70
+ value: T | null;
71
+ /** Every node removed, outermost only (a dropped node's children go with it). */
72
+ dropped: SchemaPlaceholderNode[];
73
+ /** AI note keys stripped from nodes that were kept. */
74
+ notes: Array<{
75
+ path: string;
76
+ key: string;
77
+ }>;
78
+ }
79
+ /**
80
+ * The value with its placeholder nodes removed, and what was removed. Pure; the
81
+ * input is never mutated.
82
+ */
83
+ export declare function withoutSchemaPlaceholders<T>(value: T): SchemaPlaceholderScan<T>;
84
+ /** Which nodes of a JSON-LD value are placeholders, and why. */
85
+ export declare function findSchemaPlaceholders(value: unknown): SchemaPlaceholderNode[];
86
+ /** One line a person can act on: 'Organization at $: name "Example" is a placeholder name'. */
87
+ export declare function describeSchemaPlaceholder(node: SchemaPlaceholderNode): string;
@@ -1,20 +1,21 @@
1
1
  /**
2
- * @sonordev/site-kit/engage - Engage Widget
2
+ * @sonordev/site-kit/engage - EngageWidget: deprecated, kept through 7.x.
3
3
  *
4
- * Loads and renders engagement widgets (popups, nudges, bars, chat) via Sonor API
5
- * Supports both legacy config-based rendering and new design_json from Engage Studio
4
+ * Engage was retired in Sonor. SiteKitLayout mounts website chat and popups
5
+ * for you (its `chat` and `popups` props). To place them yourself, use
6
+ * `SiteChat` from `@sonordev/site-kit/chat` and `SitePopups` from
7
+ * `@sonordev/site-kit/website/popups`. This draws exactly those two, so a
8
+ * site that still renders <EngageWidget /> keeps working. Removed in 8.0.
6
9
  */
7
10
  import React from 'react';
8
- import type { ChatLauncherPlacement } from './types';
9
- import { type AnalyticsGateOptions } from '../shared/reporting-gate';
10
- interface EngageWidgetProps extends ChatLauncherPlacement, AnalyticsGateOptions {
11
- apiUrl?: string;
12
- apiKey?: string;
13
- projectId?: string;
11
+ import { type SiteChatProps } from '../chat/SiteChat';
12
+ /** @deprecated Use SiteChat and SitePopups, or SiteKitLayout's `chat` and `popups`. */
13
+ export interface EngageWidgetProps extends SiteChatProps {
14
+ /** Show the chat launcher. Default true. */
14
15
  chatEnabled?: boolean;
15
- /** Load and show the site's popups, banners and toasts. Default true. */
16
+ /** Show the site's popups, banners and toasts. Default true. */
16
17
  popupsEnabled?: boolean;
17
18
  debug?: boolean;
18
19
  }
19
- export declare function EngageWidget(props: EngageWidgetProps): React.JSX.Element | null;
20
- export {};
20
+ /** @deprecated Use SiteChat and SitePopups, or SiteKitLayout's `chat` and `popups`. */
21
+ export declare function EngageWidget({ chatEnabled, popupsEnabled, debug, ...props }: EngageWidgetProps): React.JSX.Element;
@@ -1,13 +1,14 @@
1
1
  /**
2
- * @sonordev/site-kit/engage
2
+ * @sonordev/site-kit/engage — deprecated, kept through 7.x so old imports
3
+ * keep building. Removed in 8.0.
3
4
  *
4
- * Engagement widgets - popups, nudges, chat
5
- * DesignRenderer for Engage Studio components
5
+ * Engage was retired in Sonor. Website chat is `@sonordev/site-kit/chat`
6
+ * (Sonor → Messages) and popups are `@sonordev/site-kit/website/popups`
7
+ * (Sonor → Website). Nothing lives here: this re-exports them under the old
8
+ * names, and EngageWidget draws SiteChat and SitePopups.
6
9
  */
7
- export { EngageWidget } from './EngageWidget';
8
- export { ChatWidget } from './ChatWidget';
9
- export { DesignRenderer } from './DesignRenderer';
10
- export type { DesignDocument, DesignNode, ActionConfig, EngageContext } from './DesignRenderer';
10
+ export { EngageWidget, type EngageWidgetProps } from './EngageWidget';
11
+ export { ChatWidget } from '../chat/ChatWidget';
12
+ export { SONOR_PUBLIC_ECHO_DEFAULTS, getEchoWidgetProps } from '../chat/echo-config';
13
+ export type { EchoPublicConfig } from '../chat/echo-config';
11
14
  export * from './types';
12
- export { SONOR_PUBLIC_ECHO_DEFAULTS, getEchoWidgetProps } from './echo-config';
13
- export type { EchoPublicConfig } from './echo-config';
@@ -1,12 +1,16 @@
1
1
  'use client';
2
- export { ChatWidget } from '../chunk-RHNWA34F.js';
3
- import '../chunk-JE4A5S4F.js';
4
- export { DesignRenderer, EngageWidget } from '../chunk-YCJT4JJG.js';
5
- import '../chunk-NDF4A5JM.js';
2
+ export { SONOR_PUBLIC_ECHO_DEFAULTS, getEchoWidgetProps } from '../chunk-HODO5BX5.js';
3
+ export { ChatWidget } from '../chunk-O5RGXTWU.js';
4
+ import { SiteChat } from '../chunk-RDPW4D33.js';
6
5
  import '../chunk-6T6CQVNL.js';
7
- import '../chunk-V6FDQLR7.js';
8
- import '../chunk-AQSNPZY4.js';
6
+ import '../chunk-JE4A5S4F.js';
7
+ import { SitePopups } from '../chunk-YJLS6JBI.js';
8
+ import '../chunk-27FISYK4.js';
9
+ import '../chunk-EPT6FJUY.js';
10
+ import '../chunk-Q6E4OPGG.js';
11
+ import '../chunk-C2FZBUSS.js';
9
12
  import '../chunk-5SQK3D53.js';
13
+ import '../chunk-HW7B43E5.js';
10
14
  import '../chunk-NJCTQH2P.js';
11
15
  import '../chunk-S22FSH7C.js';
12
16
  import '../chunk-24QZEO3Q.js';
@@ -14,36 +18,28 @@ import '../chunk-L2V5PUFN.js';
14
18
  import '../chunk-GJWI74ZZ.js';
15
19
  import '../chunk-43OCZ3JA.js';
16
20
  import '../chunk-EKBEOXTH.js';
17
- import '../chunk-3SDOQUUF.js';
18
- import '../chunk-VDF5DFWT.js';
19
- import '../chunk-AH5Q262S.js';
21
+ import '../chunk-BEBR4OJD.js';
22
+ import '../chunk-WGG6GTKW.js';
23
+ import '../chunk-56KNHB4U.js';
20
24
  import '../chunk-PKBMQBKP.js';
25
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
21
26
 
22
- // src/engage/echo-config.ts
23
- var SONOR_PUBLIC_ECHO_DEFAULTS = {
24
- signalUrl: "https://signal.sonor.io",
25
- 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?",
26
- suggestedQuestions: [
27
- "What does Sonor do?",
28
- "How does Signal AI work?",
29
- "What are the pricing plans?",
30
- "Can I migrate from HubSpot?",
31
- "How does the SEO module work?"
32
- ],
33
- brandColor: "#2563eb",
34
- // blue-600 (~5.2:1 on white, AA); overridden by --sk-primary CSS var or API brand_primary
35
- assistantName: "Echo"
36
- };
37
- function getEchoWidgetProps(config = SONOR_PUBLIC_ECHO_DEFAULTS) {
38
- return {
39
- signal_enabled: true,
40
- signal_url: config.signalUrl || SONOR_PUBLIC_ECHO_DEFAULTS.signalUrl,
41
- welcome_message: config.welcomeMessage || SONOR_PUBLIC_ECHO_DEFAULTS.welcomeMessage,
42
- suggested_questions: config.suggestedQuestions || SONOR_PUBLIC_ECHO_DEFAULTS.suggestedQuestions,
43
- brand_color: config.brandColor || SONOR_PUBLIC_ECHO_DEFAULTS.brandColor,
44
- assistant_name: config.assistantName || SONOR_PUBLIC_ECHO_DEFAULTS.assistantName,
45
- assistant_avatar: config.assistantAvatar
46
- };
27
+ function EngageWidget({ chatEnabled = true, popupsEnabled = true, debug, ...props }) {
28
+ const { apiUrl, apiKey, zIndex, allowInFrame, allowLocalhost } = props;
29
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
30
+ popupsEnabled && /* @__PURE__ */ jsx(
31
+ SitePopups,
32
+ {
33
+ apiUrl,
34
+ apiKey,
35
+ zIndex,
36
+ allowInFrame,
37
+ allowLocalhost,
38
+ debug
39
+ }
40
+ ),
41
+ chatEnabled && /* @__PURE__ */ jsx(SiteChat, { ...props })
42
+ ] });
47
43
  }
48
44
 
49
- export { SONOR_PUBLIC_ECHO_DEFAULTS, getEchoWidgetProps };
45
+ export { EngageWidget };