@sonordev/site-kit 7.4.0 → 8.0.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 (227) hide show
  1. package/AGENTS.md +40 -12
  2. package/CHANGELOG.md +67 -0
  3. package/README.md +44 -18
  4. package/agent-manifest.json +111 -56
  5. package/dist/AnalyticsProvider-LYFDQRRJ.js +13 -0
  6. package/dist/{ArticleViewTracker-3DH53RSB.js → ArticleViewTracker-HG7IIRJY.js} +6 -5
  7. package/dist/{BlocksPopup-FGWYVHCH.js → BlocksPopup-GFX3GZIK.js} +6 -5
  8. package/dist/{ChatWidget-CDQZCLVH.js → ChatWidget-DVS7MLBN.js} +8 -5
  9. package/dist/{FileField-IPJ7L2W6.js → FileField-DVN7NUBI.js} +5 -5
  10. package/dist/{FormSpotlight-CQI2DBWI.js → FormSpotlight-W4SXKKO6.js} +3 -3
  11. package/dist/{FormStage-4WHD6ED4.js → FormStage-JTEE5MZD.js} +3 -3
  12. package/dist/ManagedForm-ZAUHIHGP.js +19 -0
  13. package/dist/{ManagedNewsletterForm-3D2BK3KQ.js → ManagedNewsletterForm-DQIB5Z4Y.js} +9 -6
  14. package/dist/{SignalCore-HEQYITTM.js → SignalCore-W5PYYMGX.js} +7 -7
  15. package/dist/SiteChat-MY2GATWC.js +6 -0
  16. package/dist/SiteDesignReporter-ICZWEGCY.js +12 -0
  17. package/dist/SitePopups-TGULLAER.js +13 -0
  18. package/dist/SitemapSync-AN2OBJE6.js +9 -0
  19. package/dist/_client/booking-widget.js +6 -5
  20. package/dist/_client/testimonial-section.js +3 -2
  21. package/dist/affiliates/index.js +6 -6
  22. package/dist/analytics/index.js +73 -7
  23. package/dist/articles/index.js +2 -2
  24. package/dist/articles/server-ui.js +3 -3
  25. package/dist/articles/server.js +2 -2
  26. package/dist/brand.css +1 -1
  27. package/dist/chat/index.d.ts +1 -1
  28. package/dist/chat/index.js +39 -8
  29. package/dist/chunk-3G7YABRA.js +25 -0
  30. package/dist/{chunk-CGWUXUYZ.js → chunk-3RDJKIYO.js} +2 -2
  31. package/dist/{chunk-J4FSB4OG.js → chunk-3YKQLC7J.js} +5 -2
  32. package/dist/{chunk-3E3PPSNR.js → chunk-56VDS2RP.js} +2 -2
  33. package/dist/chunk-5ROTTMBL.js +19 -0
  34. package/dist/{chunk-KHQ5GD6T.js → chunk-5S2XDG5O.js} +1 -1
  35. package/dist/chunk-5TM3WPCI.js +7 -0
  36. package/dist/{chunk-7HPZSWS4.js → chunk-6CJGZFOU.js} +1 -1
  37. package/dist/{chunk-RYVDGXC2.js → chunk-6KU7STM3.js} +3 -1
  38. package/dist/{chunk-FUQTV5O6.js → chunk-6QFINXLH.js} +3 -3
  39. package/dist/{chunk-AH4AXO4P.js → chunk-6ZXJ3SXR.js} +1 -1
  40. package/dist/{chunk-XA2BNW5M.js → chunk-7YEEOIWI.js} +1 -1
  41. package/dist/{chunk-IE5SSOCN.js → chunk-BQ2CXKTQ.js} +3 -11
  42. package/dist/{chunk-X6F6SO4S.js → chunk-C7TLGL3R.js} +21 -1
  43. package/dist/{chunk-43OCZ3JA.js → chunk-CAH4Y4PY.js} +4 -2
  44. package/dist/{chunk-PXKIGZ2F.js → chunk-EATJPSTS.js} +1 -1
  45. package/dist/{chunk-OOD6GMT4.js → chunk-GNSXOTIK.js} +6 -4
  46. package/dist/{chunk-EHR4NIMX.js → chunk-ICAVVF6D.js} +3 -3
  47. package/dist/{chunk-MHGAZ3LL.js → chunk-IJQ767JS.js} +3 -5
  48. package/dist/{chunk-CKW4HFJG.js → chunk-J65N34YM.js} +3 -3
  49. package/dist/{chunk-4BHRA7TI.js → chunk-JIH32RH2.js} +3 -3
  50. package/dist/{chunk-D2RGEWHS.js → chunk-K22OLAPC.js} +2 -2
  51. package/dist/{chunk-CG4ETW5Y.js → chunk-KRBBH64S.js} +3 -3
  52. package/dist/{chunk-JRCNUUNG.js → chunk-LBIQ5LTC.js} +1 -1
  53. package/dist/{chunk-MDP7EB4J.js → chunk-LKH44GI3.js} +5 -5
  54. package/dist/{chunk-ZTLMPUO6.js → chunk-LNSF6BJC.js} +1 -1
  55. package/dist/{chunk-5O62CER4.js → chunk-M62U7TJF.js} +1 -1
  56. package/dist/{chunk-EDYHROQ7.js → chunk-NFRJTOTB.js} +3 -3
  57. package/dist/{chunk-4DJMJ4V5.js → chunk-OE3NXEOF.js} +1 -1
  58. package/dist/{chunk-AV4QAK5F.js → chunk-OGK7OL3T.js} +5 -4
  59. package/dist/{chunk-YCFW2OQO.js → chunk-PD7K54EQ.js} +2 -2
  60. package/dist/{chunk-PF64OEXC.js → chunk-PIX7FAMX.js} +2 -2
  61. package/dist/{chunk-XEMHGJEG.js → chunk-ROAOI6GG.js} +10 -10
  62. package/dist/{chunk-6LLTLLEJ.js → chunk-S7SSYGUJ.js} +6 -5
  63. package/dist/{chunk-ATE6IFVK.js → chunk-SBPAXWLF.js} +1 -1
  64. package/dist/{chunk-DA5XC7Y6.js → chunk-TOSZGJEQ.js} +1 -1
  65. package/dist/{chunk-4NTBQNHA.js → chunk-TYAVHRWH.js} +80 -81
  66. package/dist/{chunk-ZTLU3BSW.js → chunk-TZ5RTANX.js} +2 -2
  67. package/dist/{chunk-4YTYGG2C.js → chunk-URJN75ZG.js} +29 -16
  68. package/dist/{chunk-CSN45MHO.js → chunk-UUWUAMUC.js} +13 -16
  69. package/dist/{chunk-PKGN32AU.js → chunk-XPTJRVL2.js} +1 -1
  70. package/dist/{chunk-662ILEZ6.js → chunk-Y35BWW2C.js} +3 -7
  71. package/dist/{chunk-PQDJNIHR.js → chunk-ZHNQFNO3.js} +2 -2
  72. package/dist/chunk-ZKADJNR2.js +52 -0
  73. package/dist/{chunk-GK7TV7EK.js → chunk-ZLFGXKLG.js} +1 -1
  74. package/dist/client/index.js +6 -5
  75. package/dist/contracts/entries.d.ts +1 -1
  76. package/dist/contracts/sentences.d.ts +46 -0
  77. package/dist/contracts/site-cache.d.ts +2 -0
  78. package/dist/cta-bar/index.js +1 -1
  79. package/dist/fleet/index.js +6 -5
  80. package/dist/forms/field-autocomplete.d.ts +16 -8
  81. package/dist/forms/index.d.ts +5 -0
  82. package/dist/forms/index.js +17 -17
  83. package/dist/forms/server.js +7 -7
  84. package/dist/forms/static.js +2 -2
  85. package/dist/forms/submitForm.d.ts +5 -0
  86. package/dist/images/index.js +4 -4
  87. package/dist/index.d.ts +3 -7
  88. package/dist/index.js +1 -1
  89. package/dist/layout/SiteKitLayout.d.ts +2 -1
  90. package/dist/layout/client.js +11 -9
  91. package/dist/layout/index.js +13 -11
  92. package/dist/llms/index.js +7 -6
  93. package/dist/maps/index.js +6 -6
  94. package/dist/mcp/sonor.d.ts +47 -3
  95. package/dist/mcp/sonor.js +30 -20
  96. package/dist/proxy/index.d.ts +3 -3
  97. package/dist/proxy/index.js +1 -1
  98. package/dist/reputation/index.js +3 -2
  99. package/dist/reputation/server.js +2 -1
  100. package/dist/revalidate/index.js +3 -3
  101. package/dist/robots/indexnow.js +2 -2
  102. package/dist/runtime/index.d.ts +32 -0
  103. package/dist/runtime/index.js +7 -0
  104. package/dist/seo/api.d.ts +0 -4
  105. package/dist/seo/client.js +6 -5
  106. package/dist/seo/getManagedMetadata.d.ts +24 -0
  107. package/dist/seo/index.d.ts +2 -3
  108. package/dist/seo/index.js +14 -132
  109. package/dist/seo/indexnow.js +2 -2
  110. package/dist/seo/llms.js +7 -6
  111. package/dist/seo/register-sitemap-cli.js +2 -2
  112. package/dist/seo/server-api.d.ts +0 -6
  113. package/dist/seo/server.d.ts +1 -1
  114. package/dist/seo/server.js +4 -4
  115. package/dist/seo/sitemap.js +4 -3
  116. package/dist/seo/types.d.ts +9 -30
  117. package/dist/server/index.d.ts +8 -1
  118. package/dist/server/index.js +4 -3
  119. package/dist/server/mint-site-token.d.ts +0 -11
  120. package/dist/server/server-fetch.d.ts +19 -0
  121. package/dist/server-api-EGOKC7Q3.js +9 -0
  122. package/dist/shared/build-entries.d.ts +21 -9
  123. package/dist/shared/clientApiConfig.d.ts +56 -0
  124. package/dist/shared/identity-reader.d.ts +26 -0
  125. package/dist/shared/identity-storage.d.ts +30 -0
  126. package/dist/shared/identity.d.ts +13 -13
  127. package/dist/shared/import-specifiers.d.ts +26 -0
  128. package/dist/shared/sonorFetch.d.ts +4 -3
  129. package/dist/shared/version.d.ts +1 -1
  130. package/dist/signal/index.js +2 -2
  131. package/dist/sitemap/index.js +4 -3
  132. package/dist/slots/index.js +3 -3
  133. package/dist/sync/index.js +6 -5
  134. package/dist/types.d.ts +0 -1
  135. package/dist/website/cta-bar.js +1 -1
  136. package/dist/website/images.js +4 -4
  137. package/dist/website/index.js +7 -6
  138. package/dist/website/popups.js +10 -7
  139. package/dist/website/slots.js +3 -3
  140. package/dist/{writeLLMsTxt-OL4KQERZ.js → writeLLMsTxt-UZGN6IBU.js} +3 -2
  141. package/docs/MIGRATING-TO-7.md +5 -2
  142. package/docs/MIGRATING-TO-8.md +183 -0
  143. package/docs.json +4 -8
  144. package/package.json +15 -44
  145. package/skills/site-kit/SKILL.md +41 -11
  146. package/src/analytics/README.md +14 -14
  147. package/src/chat/README.md +7 -7
  148. package/src/forms/README.md +61 -6
  149. package/src/mcp/README.md +61 -20
  150. package/src/proxy/README.md +5 -4
  151. package/src/revalidate/README.md +2 -2
  152. package/src/runtime/README.md +51 -0
  153. package/src/seo/README.md +24 -8
  154. package/src/sync/README.md +2 -2
  155. package/src/website/README.md +3 -2
  156. package/dist/AnalyticsProvider-BQXV3ZF3.js +0 -11
  157. package/dist/ManagedForm-7FZNH4L2.js +0 -16
  158. package/dist/SiteChat-DMPDXY3W.js +0 -5
  159. package/dist/SiteDesignReporter-Y2IOJE52.js +0 -11
  160. package/dist/SitePopups-HD2LDKVU.js +0 -10
  161. package/dist/SitemapSync-OKPZHRX6.js +0 -8
  162. package/dist/chunk-24QZEO3Q.js +0 -41
  163. package/dist/chunk-BU66S5B7.js +0 -1
  164. package/dist/chunk-G3NHWMP5.js +0 -233
  165. package/dist/chunk-HODO5BX5.js +0 -28
  166. package/dist/chunk-L3AQCDCY.js +0 -386
  167. package/dist/chunk-P5GB6NEO.js +0 -66
  168. package/dist/chunk-SWKE6FWH.js +0 -47
  169. package/dist/cms/CmsPage.d.ts +0 -17
  170. package/dist/cms/CmsPreview.d.ts +0 -37
  171. package/dist/cms/CmsSection.d.ts +0 -8
  172. package/dist/cms/PortableTextRenderer.d.ts +0 -7
  173. package/dist/cms/index.d.ts +0 -37
  174. package/dist/cms/index.js +0 -5
  175. package/dist/cms/sanity-image.d.ts +0 -47
  176. package/dist/cms/sections/CtaSection.d.ts +0 -3
  177. package/dist/cms/sections/CustomSection.d.ts +0 -7
  178. package/dist/cms/sections/FaqSection.d.ts +0 -3
  179. package/dist/cms/sections/FormSection.d.ts +0 -8
  180. package/dist/cms/sections/GallerySection.d.ts +0 -3
  181. package/dist/cms/sections/HeroSection.d.ts +0 -3
  182. package/dist/cms/sections/RichTextSection.d.ts +0 -3
  183. package/dist/cms/sections/TestimonialsSection.d.ts +0 -3
  184. package/dist/cms/sections/index.d.ts +0 -8
  185. package/dist/cms/server-api.d.ts +0 -49
  186. package/dist/cms/server.d.ts +0 -1
  187. package/dist/cms/server.js +0 -5
  188. package/dist/cms/types.d.ts +0 -104
  189. package/dist/commerce/CalendarView.d.ts +0 -43
  190. package/dist/commerce/CheckoutForm.d.ts +0 -11
  191. package/dist/commerce/EventCalendar.d.ts +0 -26
  192. package/dist/commerce/EventCheckout.d.ts +0 -42
  193. package/dist/commerce/EventEmbed.d.ts +0 -11
  194. package/dist/commerce/EventModal.d.ts +0 -43
  195. package/dist/commerce/EventTile.d.ts +0 -10
  196. package/dist/commerce/EventsAgenda.d.ts +0 -38
  197. package/dist/commerce/EventsWidget.d.ts +0 -84
  198. package/dist/commerce/OfferingCard.d.ts +0 -9
  199. package/dist/commerce/OfferingList.d.ts +0 -9
  200. package/dist/commerce/ProductDetail.d.ts +0 -38
  201. package/dist/commerce/ProductEmbed.d.ts +0 -11
  202. package/dist/commerce/ProductGrid.d.ts +0 -41
  203. package/dist/commerce/ProductPage.d.ts +0 -39
  204. package/dist/commerce/RegistrationForm.d.ts +0 -9
  205. package/dist/commerce/SizeChart.d.ts +0 -8
  206. package/dist/commerce/UpcomingEvents.d.ts +0 -9
  207. package/dist/commerce/api.d.ts +0 -179
  208. package/dist/commerce/events-extras.d.ts +0 -60
  209. package/dist/commerce/index.d.ts +0 -33
  210. package/dist/commerce/index.js +0 -8022
  211. package/dist/commerce/server.d.ts +0 -161
  212. package/dist/commerce/server.js +0 -2
  213. package/dist/commerce/types.d.ts +0 -340
  214. package/dist/commerce/useEventModal.d.ts +0 -20
  215. package/dist/commerce/utils.d.ts +0 -17
  216. package/dist/engage/EngageWidget.d.ts +0 -21
  217. package/dist/engage/index.d.ts +0 -14
  218. package/dist/engage/index.js +0 -45
  219. package/dist/engage/types.d.ts +0 -33
  220. package/dist/seo/ManagedContent.d.ts +0 -14
  221. package/dist/server-api-BVCBLJKL.js +0 -9
  222. package/dist/website/cms/server.d.ts +0 -2
  223. package/dist/website/cms/server.js +0 -5
  224. package/dist/website/cms-server.d.ts +0 -2
  225. package/dist/website/cms.d.ts +0 -2
  226. package/dist/website/cms.js +0 -5
  227. package/src/commerce/README.md +0 -109
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@sonordev/site-kit",
3
- "version": "7.4.0",
3
+ "version": "8.0.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@11.5.3",
6
- "description": "Complete client-side integration kit for Sonor - SEO, Analytics, Engage, Forms, Blog",
6
+ "description": "The Sonor modules a Next.js site needs, from one package and one API key: SEO, analytics, forms, articles, chat, booking, AI visibility and agent tools. Selling online? Add @sonordev/commerce-kit.",
7
7
  "license": "MIT",
8
8
  "repository": {
9
9
  "type": "git",
@@ -13,7 +13,7 @@
13
13
  "**/*.css"
14
14
  ],
15
15
  "main": "./dist/index.js",
16
- "module": "./dist/index.mjs",
16
+ "module": "./dist/index.js",
17
17
  "types": "./dist/index.d.ts",
18
18
  "bin": {
19
19
  "sonor-register-sitemap": "dist/seo/register-sitemap-cli.js"
@@ -43,10 +43,6 @@
43
43
  "types": "./dist/analytics/index.d.ts",
44
44
  "default": "./dist/analytics/index.js"
45
45
  },
46
- "./engage": {
47
- "types": "./dist/engage/index.d.ts",
48
- "default": "./dist/engage/index.js"
49
- },
50
46
  "./signal": {
51
47
  "types": "./dist/signal/index.d.ts",
52
48
  "default": "./dist/signal/index.js"
@@ -75,14 +71,6 @@
75
71
  "types": "./dist/articles/server-ui.d.ts",
76
72
  "default": "./dist/articles/server-ui.js"
77
73
  },
78
- "./commerce": {
79
- "types": "./dist/commerce/index.d.ts",
80
- "default": "./dist/commerce/index.js"
81
- },
82
- "./commerce/server": {
83
- "types": "./dist/commerce/server.d.ts",
84
- "default": "./dist/commerce/server.js"
85
- },
86
74
  "./sync": {
87
75
  "types": "./dist/sync/index.d.ts",
88
76
  "default": "./dist/sync/index.js"
@@ -207,14 +195,6 @@
207
195
  "types": "./dist/website/slots/contract.d.ts",
208
196
  "default": "./dist/website/slots/contract.js"
209
197
  },
210
- "./website/cms": {
211
- "types": "./dist/website/cms.d.ts",
212
- "default": "./dist/website/cms.js"
213
- },
214
- "./website/cms/server": {
215
- "types": "./dist/website/cms/server.d.ts",
216
- "default": "./dist/website/cms/server.js"
217
- },
218
198
  "./website/landing": {
219
199
  "types": "./dist/website/landing.d.ts",
220
200
  "default": "./dist/website/landing.js"
@@ -323,14 +303,6 @@
323
303
  "types": "./dist/config/index.d.ts",
324
304
  "default": "./dist/config/index.js"
325
305
  },
326
- "./cms": {
327
- "types": "./dist/cms/index.d.ts",
328
- "default": "./dist/cms/index.js"
329
- },
330
- "./cms/server": {
331
- "types": "./dist/cms/server.d.ts",
332
- "default": "./dist/cms/server.js"
333
- },
334
306
  "./maps": {
335
307
  "types": "./dist/maps/index.d.ts",
336
308
  "default": "./dist/maps/index.js"
@@ -355,6 +327,10 @@
355
327
  "types": "./dist/client/index.d.ts",
356
328
  "default": "./dist/client/index.js"
357
329
  },
330
+ "./runtime": {
331
+ "types": "./dist/runtime/index.d.ts",
332
+ "default": "./dist/runtime/index.js"
333
+ },
358
334
  "./brand.css": "./dist/brand.css",
359
335
  "./forms/styles.css": "./dist/forms/styles.css",
360
336
  "./forms/static": {
@@ -375,6 +351,7 @@
375
351
  "docs.json",
376
352
  "CHANGELOG.md",
377
353
  "docs/MIGRATING-TO-7.md",
354
+ "docs/MIGRATING-TO-8.md",
378
355
  "src/*/README.md"
379
356
  ],
380
357
  "scripts": {
@@ -395,13 +372,15 @@
395
372
  "typecheck": "tsc --noEmit",
396
373
  "typecheck:setup": "tsc --noEmit -p packages/sonor-setup/tsconfig.json",
397
374
  "version": "node scripts/sync-version.cjs && node scripts/gen-agent-manifest.cjs && git add src/shared/version.ts agent-manifest.json",
398
- "prepublishOnly": "rm -rf dist && NODE_OPTIONS=--max-old-space-size=8192 tsup && pnpm build:types && node scripts/gen-agent-manifest.cjs && node scripts/verify-dts.cjs && node scripts/prepublish-integration.cjs && node scripts/audit-as-consumer.cjs && node scripts/verify-docs.cjs",
375
+ "prepublishOnly": "vitest run && tsc --noEmit -p packages/commerce-kit/tsconfig.json && rm -rf dist && NODE_OPTIONS=--max-old-space-size=8192 tsup && pnpm build:types && node scripts/gen-agent-manifest.cjs && node scripts/verify-dts.cjs && node scripts/verify-no-local-paths.cjs dist agent-manifest.json && node scripts/prepublish-integration.cjs && node scripts/audit-as-consumer.cjs && node scripts/verify-docs.cjs",
399
376
  "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly --noEmit false && node scripts/alias-dts.cjs",
400
- "verify:docs": "node scripts/verify-docs.cjs"
377
+ "verify:docs": "node scripts/verify-docs.cjs",
378
+ "build:commerce": "node scripts/check-kit-build-order.cjs && NODE_OPTIONS=--max-old-space-size=8192 tsup --config packages/commerce-kit/tsup.config.ts && tsc -p packages/commerce-kit/tsconfig.build.json && node packages/commerce-kit/scripts/verify-dist.cjs && node packages/commerce-kit/scripts/verify-against-site-kit.cjs",
379
+ "test:commerce": "vitest run packages/commerce-kit && tsc --noEmit -p packages/commerce-kit/tsconfig.json",
380
+ "typecheck:commerce": "tsc --noEmit -p packages/commerce-kit/tsconfig.json",
381
+ "verify:docs:commerce": "node scripts/verify-docs.cjs packages/commerce-kit"
401
382
  },
402
383
  "peerDependencies": {
403
- "@portabletext/react": "^8.0.0",
404
- "@sanity/image-url": "^2.1.1",
405
384
  "@vis.gl/react-google-maps": ">=1.0.0",
406
385
  "gsap": "^3.13.0",
407
386
  "next": "^16.0.0",
@@ -422,12 +401,6 @@
422
401
  "@vis.gl/react-google-maps": {
423
402
  "optional": true
424
403
  },
425
- "@portabletext/react": {
426
- "optional": true
427
- },
428
- "@sanity/image-url": {
429
- "optional": true
430
- },
431
404
  "gsap": {
432
405
  "optional": true
433
406
  },
@@ -446,8 +419,6 @@
446
419
  "@babel/parser": "^8.0.4",
447
420
  "@babel/traverse": "^8.0.4",
448
421
  "@babel/types": "^8.0.4",
449
- "@portabletext/react": "^8.0.0",
450
- "@sanity/image-url": "^2.1.1",
451
422
  "@types/babel__generator": "^7.27.0",
452
423
  "@types/babel__traverse": "^7.28.0",
453
424
  "@types/inquirer": "^9.0.10",
@@ -480,7 +451,7 @@
480
451
  "seo",
481
452
  "analytics",
482
453
  "forms",
483
- "engage",
454
+ "chat",
484
455
  "articles",
485
456
  "publishing",
486
457
  "nextjs",
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: site-kit
3
- description: Integrate, upgrade, or debug a Next.js site's Sonor integration via @sonordev/site-kit. Use when wiring a site to Sonor, migrating a 2.x integration to 3.x, or diagnosing SSR/analytics/key issues on a site that installs @sonordev/site-kit. Trigger on "wire up Sonor", "connect this site to Sonor", "site-kit", "sonor-setup", "SiteKitLayout", "analytics bailout / CSR", or a failing `sonor-setup verify`.
3
+ description: Integrate, upgrade, or debug a Next.js site's Sonor integration via @sonordev/site-kit. Use when wiring a site to Sonor, migrating a 2.x integration to 3.x, or diagnosing SSR/analytics/key issues on a site that installs @sonordev/site-kit. Also use when moving a site to site-kit 8, or when adding commerce, events or memberships through @sonordev/commerce-kit. Trigger on "wire up Sonor", "connect this site to Sonor", "site-kit", "sonor-setup", "SiteKitLayout", "commerce-kit", "analytics bailout / CSR", or a failing `sonor-setup verify`.
4
4
  ---
5
5
 
6
- # @sonordev/site-kit — integration skill
6
+ # @sonordev/site-kit: integration skill
7
7
 
8
8
  You are integrating a Next.js site with Sonor using `@sonordev/site-kit`. The
9
9
  package ships its own machine-readable contract; use it instead of guessing.
@@ -16,7 +16,7 @@ package ships its own machine-readable contract; use it instead of guessing.
16
16
  (`verify`, `doctor`, `codemod`, `manifest`, `init`, `install`, `upgrade`)
17
17
  prints ONE JSON envelope to stdout. Parse it; branch on `exitCode`/`checks`.
18
18
  3. **Never let a command prompt.** Pass `--yes` (and `--api-key` to `init`) so it
19
- runs non-interactively. Exit `3` means you're missing a flag — the error's
19
+ runs non-interactively. Exit `3` means you're missing a flag; the error's
20
20
  `fix` names it.
21
21
  4. **One env var: `SONOR_API_KEY`** (server-side, no `NEXT_PUBLIC_` prefix). Never
22
22
  add `UPTRADE_*`, `NEXT_PUBLIC_SONOR_API_KEY`, or `SONOR_PROJECT_ID`.
@@ -25,7 +25,7 @@ package ships its own machine-readable contract; use it instead of guessing.
25
25
  anywhere below the fold; `./motion/gsap` and `./motion/three` need their
26
26
  peer installed first (`npm i gsap` / `npm i three`). Never wrap page
27
27
  content in a `dynamic(ssr:false)` component and never animate the hero
28
- in — the server HTML must stay fully visible. See `src/motion/README.md`.
28
+ in; the server HTML must stay fully visible. See `src/motion/README.md`.
29
29
  6. **The sticky mobile CTA bar comes from `@sonordev/site-kit/cta-bar`, never a
30
30
  per-site `MobileCTABar`.** Render `<CtaBar>` once per page (the layout root
31
31
  for a site-wide bar, inside the page for a page-specific one), with
@@ -33,8 +33,17 @@ package ships its own machine-readable contract; use it instead of guessing.
33
33
  own bar, its body `padding-bottom`, and any `--sk-echo-offset-bottom` rule
34
34
  written for it: the kit reserves the space and lifts the Echo launcher.
35
35
  See `src/cta-bar/README.md`.
36
-
37
- ## First step — discover, don't assume
36
+ 7. **Commerce, events and memberships come from `@sonordev/commerce-kit`, not
37
+ site-kit (since site-kit 8).** Install it beside site-kit with
38
+ `npm i @sonordev/commerce-kit` and import from
39
+ `@sonordev/commerce-kit/commerce`, `/commerce/server`, `/memberships` or
40
+ `/memberships/server`. The old site-kit commerce entries are gone, and
41
+ `npx sonor-setup manifest --json` lists every moved subpath under
42
+ `relocated`, the root types that moved under `relocatedTypes`, and what was
43
+ retired for good (Engage, the CMS, content blocks) under `removed`.
44
+ Docs: https://sonor.dev/commerce-kit.
45
+
46
+ ## First step: discover, don't assume
38
47
 
39
48
  ```bash
40
49
  cat node_modules/@sonordev/site-kit/AGENTS.md # human-skimmable guide
@@ -55,7 +64,7 @@ npx sonor-setup verify --json
55
64
 
56
65
  ```bash
57
66
  npx sonor-setup codemod --check --json # exit 1 ⇒ work pending
58
- npx sonor-setup codemod --write --json # apply (idempotent; writes .bak). Review the diff.
67
+ npx sonor-setup codemod --write --json # apply (idempotent; originals go to a temp folder). Review the diff.
59
68
  next build # a bump you didn't build is a guess
60
69
  npx sonor-setup verify --json
61
70
  ```
@@ -69,22 +78,43 @@ Codemod transforms: `provider-to-layout` (SiteKitProvider→SiteKitLayout),
69
78
  (flags an `<AnalyticsProvider>` wrapped around `{children}`; remove it by hand.
70
79
  A plain `<SiteKitLayout>{children}</SiteKitLayout>` is correct and isn't flagged).
71
80
 
81
+ ## Move a 7.x site that sells to site-kit 8
82
+
83
+ Commerce left site-kit in 8.0 (memberships is new, and ships only in
84
+ commerce-kit). A site that doesn't use commerce needs nothing but the version bump.
85
+
86
+ ```bash
87
+ npm i @sonordev/site-kit@^8 @sonordev/commerce-kit
88
+ npx sonor-setup codemod --only site-kit-8 --check --json # exit 1 ⇒ imports still to move
89
+ npx sonor-setup codemod --only site-kit-8 --write --json # swaps the package name in each import
90
+ next build
91
+ npx sonor-setup verify --json
92
+ ```
93
+
94
+ It rewrites the imports, adds `@sonordev/commerce-kit` to package.json, raises
95
+ site-kit's range to `^8.0.0` and flags what it can't rewrite. A site that passes
96
+ `offerings` to `sonorMcpServer` also needs `read: getOfferingsResult`, imported from
97
+ `@sonordev/commerce-kit/commerce/server`. See `docs/MIGRATING-TO-8.md`.
98
+
72
99
  ## Debug a red integration
73
100
 
74
101
  Run `npx sonor-setup doctor --online --json` (or `verify`). Each `checks[]`
75
102
  finding has a stable `id`, a `fix`, and often a `fixCommand`. Highest-signal ids:
76
103
 
77
- - `ssr.render` — page bailed to client rendering. On site-kit ≥3.0.2 a plain
104
+ - `ssr.render`: page bailed to client rendering. On site-kit ≥3.0.2 a plain
78
105
  `SiteKitLayout` is never the cause: it already mounts analytics as a
79
106
  deferred, childless sibling, so don't reach for `analytics={false}`. Find
80
107
  what else wraps `{children}` (usually a `next/dynamic({ ssr: false })`
81
108
  component, often the site's own Providers) and import it statically or mount
82
109
  it as a childless sibling. On <3.0.2, upgrade. Confirm with
83
110
  `verify --url <url>`.
84
- - `key.valid` — key rejected/stale. Put the current `sonor_` key in `.env.local`
111
+ - `key.valid`: key rejected/stale. Put the current `sonor_` key in `.env.local`
85
112
  and redeploy (hosts don't pick up env changes without a redeploy).
86
- - `middleware.netlify` — `middleware.ts` sets `runtime: 'nodejs'`, which
113
+ - `middleware.netlify`: `middleware.ts` sets `runtime: 'nodejs'`, which
87
114
  `proxy.ts` rejects. Remove the export, then run the codemod to move it.
88
- - `layout.sitekit` — deprecated `SiteKitProvider`; run the codemod.
115
+ - `layout.sitekit`: deprecated `SiteKitProvider`; run the codemod.
116
+ - A build that can't resolve a site-kit commerce path: the site is on site-kit 8,
117
+ where commerce moved to `@sonordev/commerce-kit`. Run the site-kit 8 steps
118
+ above.
89
119
 
90
120
  Apply fixes, then re-run `verify --json`. Done when it exits 0.
@@ -1,4 +1,4 @@
1
- # Analytics — `@sonordev/site-kit/analytics`
1
+ # Analytics: `@sonordev/site-kit/analytics`
2
2
 
3
3
  Automatic page view tracking, custom events, conversions, scroll depth, heatmap clicks, and Core Web Vitals. All data flows through the Sonor API.
4
4
 
@@ -157,22 +157,22 @@ interface AnalyticsConfig {
157
157
  trackScrollDepth?: boolean // Default: true
158
158
  sessionTimeout?: number // Minutes (default: 30)
159
159
  excludePaths?: string[] // Don't track these paths
160
- allowInFrame?: boolean // Default: false — see below
161
- allowLocalhost?: boolean // Default: false — local builds report nothing
160
+ allowInFrame?: boolean // Default: false (see below)
161
+ allowLocalhost?: boolean // Default: false (local builds report nothing)
162
162
  debug?: boolean // Log events to console
163
163
  }
164
164
  ```
165
165
 
166
166
  ## What Gets Tracked Automatically
167
167
 
168
- - **Page views** — on every route change (path, URL, title, referrer, UTM params, device/browser/OS)
169
- - **Web Vitals** — LCP, CLS, TTFB, INP, FCP with good/needs-improvement/poor ratings
170
- - **Contact clicks** — `tel:` and `mailto:` links tracked as conversions, once `<ContactTracking />` is mounted (it isn't by default)
171
- - **DOM metadata** — full snapshot per page view (meta tags, H1, word count, links, content, FAQs)
168
+ - **Page views**: on every route change (path, URL, title, referrer, UTM params, device/browser/OS)
169
+ - **Web Vitals**: LCP, CLS, TTFB, INP, FCP with good/needs-improvement/poor ratings
170
+ - **Contact clicks**: `tel:` and `mailto:` links tracked as conversions, once `<ContactTracking />` is mounted (it isn't by default)
171
+ - **DOM metadata**: full snapshot per page view (meta tags, H1, word count, links, content, FAQs)
172
172
 
173
173
  ## Embedded pages report nothing (`analytics.allowInFrame`)
174
174
 
175
- When this site is loaded inside a **cross-origin iframe**, nothing is sent —
175
+ When this site is loaded inside a **cross-origin iframe**, nothing is sent:
176
176
  no page views, journey/session rows, scroll depth, heatmap clicks, web vitals,
177
177
  events or conversions. The visitor is on whoever framed the page, not on this
178
178
  site, so every metric the frame produces is phantom traffic in the analytics
@@ -189,8 +189,8 @@ the pixels, not the JavaScript.
189
189
  a print view, an on-domain booking frame) has a real visitor really on that
190
190
  site and no other tenant to pollute.
191
191
 
192
- Opt back in only when the frame IS the product — a widget or partner-hosted
193
- page deliberately distributed as an embed:
192
+ Opt back in only when the frame IS the product (a widget or partner-hosted
193
+ page deliberately distributed as an embed):
194
194
 
195
195
  ```tsx
196
196
  <SiteKitLayout analytics={{ allowInFrame: true }}>…</SiteKitLayout>
@@ -199,7 +199,7 @@ page deliberately distributed as an embed:
199
199
  There is deliberately no env var or window global for this. A silent switch
200
200
  that turns cross-tenant tracking back on is the failure mode, not the feature.
201
201
 
202
- The decision lives in one place — `shared/reporting-gate.ts`, over the frame
202
+ The decision lives in one place, `shared/reporting-gate.ts`, over the frame
203
203
  primitive in `shared/frame.ts`. Every send in the module routes through it, and
204
204
  `send-gate.test.ts` fails the build if a new one does not. `isCrossOriginFrame()`
205
205
  is exported from `@sonordev/site-kit/analytics` if a site needs the same answer
@@ -229,8 +229,8 @@ Neither option bypasses authentication. No environment variable or global
229
229
  silently enables local reporting.
230
230
 
231
231
  This is a browser gate: build-time `createSitemap` and Node fleet reporting
232
- still run. It doesn't disable forms, commerce, or Signal. When verifying those
233
- modules locally, point them at a test API as well.
232
+ still run. It doesn't disable forms, Signal, or [commerce-kit](https://sonor.dev/commerce-kit).
233
+ When verifying those modules locally, point them at a test API as well.
234
234
 
235
235
  ## Environment
236
236
 
@@ -257,6 +257,6 @@ site without forcing each microsite into its own Sonor project.
257
257
  </SiteKitLayout>
258
258
  ```
259
259
 
260
- Most projects leave it implicit — `NEXT_PUBLIC_SITE_URL` is already set per
260
+ Most projects leave it implicit: `NEXT_PUBLIC_SITE_URL` is already set per
261
261
  microsite, so the dimension fills in automatically. The dashboard shows a
262
262
  "Site" picker + a "Sites" tab as soon as ≥2 distinct hosts are detected.
@@ -46,7 +46,7 @@ interface SiteChatProps {
46
46
  ```
47
47
 
48
48
  Through `SiteKitLayout`, the placement options go in `chat={{ ... }}`. The
49
- deprecated `engage={{ ... }}` still works through 7.x.
49
+ deprecated `engage={{ ... }}` still works; it goes in a later major.
50
50
 
51
51
  ## The chat switch
52
52
 
@@ -233,10 +233,10 @@ interface ChatConfig {
233
233
  Popups, banners and toasts are their own module now:
234
234
  [Popups and banners](../website/README.md) (`@sonordev/site-kit/website/popups`).
235
235
 
236
- ## `@sonordev/site-kit/engage` (deprecated)
236
+ ## Engage (retired)
237
237
 
238
- Engage was retired in Sonor. Its entry stays through 7.x so old imports keep
239
- building: `ChatWidget` and the chat types re-export from here, and
240
- `EngageWidget` draws `SiteChat` and `SitePopups`. Engage Studio's renderer
241
- (`DesignRenderer`) is gone; popups render from blocks. Import
242
- `@sonordev/site-kit/chat` and `@sonordev/site-kit/website/popups` instead.
238
+ Engage was retired in Sonor, and `@sonordev/site-kit/engage` was removed in
239
+ site-kit 8. `ChatWidget` and the chat types are here, popups are in
240
+ `@sonordev/site-kit/website/popups`, and `SiteKitLayout` mounts both for you.
241
+ Engage Studio's renderer (`DesignRenderer`) is gone; popups render from blocks.
242
+ `npx sonor-setup codemod --write` moves the names that have a home.
@@ -1,4 +1,4 @@
1
- # Forms — `@sonordev/site-kit/forms`
1
+ # Forms: `@sonordev/site-kit/forms`
2
2
 
3
3
  Sonor-managed forms with multi-step support, conditional logic, validation, anti-bot protection, and automatic CRM routing.
4
4
 
@@ -21,7 +21,7 @@ Fetches form config from Sonor, renders fields, handles submission, routes to CR
21
21
  Since 4.0 that one line gets you the **spotlight** experience by default: the
22
22
  familiar layout, alive. A glowing ring visits the field you're in, completed
23
23
  fields earn a check, and each finished row collapses into a sentence the form
24
- says back — "Nice to meet you, Jordan." / "We'll follow up at jordan@…" — with
24
+ says back ("Nice to meet you, Jordan." or "We'll follow up at jordan@…"), with
25
25
  an edit control to reopen it.
26
26
 
27
27
  | Experience | What it is | How to get it |
@@ -36,7 +36,7 @@ an edit control to reopen it.
36
36
  <ManagedForm formId="contact-form" experience="classic" /> // opt out
37
37
  ```
38
38
 
39
- Sonor can decide instead of the site, with no deploy on either side — set the
39
+ Sonor can decide instead of the site, with no deploy on either side: set the
40
40
  form's `layout` to `classic`, `stage`, or `spotlight`. An explicit
41
41
  `experience` prop always wins over the config.
42
42
 
@@ -68,7 +68,7 @@ Welcome aboard, {first_name}. We'll reach you at {email}.
68
68
  ```
69
69
 
70
70
  Leave it empty to keep the built-in sentence. Unknown or unanswered tokens
71
- render as nothing — never raw braces.
71
+ render as nothing, never raw braces.
72
72
 
73
73
  ### Option 2: Headless Hook (full UI control)
74
74
 
@@ -101,6 +101,59 @@ export function ContactForm() {
101
101
  }
102
102
  ```
103
103
 
104
+ #### Autocomplete on your own controls
105
+
106
+ `ManagedForm` puts the right `autocomplete` token on each field that has one, so
107
+ browser autofill, password managers and AI assistants know what the field wants.
108
+ When you draw the controls yourself, with `useForm` or the render prop below, ask
109
+ for the same token with `autocompleteFor` (since 8.0.0) and pass it to
110
+ `autoComplete`:
111
+
112
+ ```tsx
113
+ 'use client'
114
+ import { useForm, autocompleteFor } from '@sonordev/site-kit/forms'
115
+
116
+ export function QuoteForm() {
117
+ const { fields, values, setFieldValue, handleSubmit, toolAttributes } = useForm('quote-request')
118
+
119
+ return (
120
+ <form {...toolAttributes} onSubmit={handleSubmit}>
121
+ {fields.map(field => (
122
+ <label key={field.slug}>
123
+ {field.label}
124
+ <input
125
+ name={field.slug}
126
+ autoComplete={autocompleteFor(field)}
127
+ value={String(values[field.slug] || '')}
128
+ onChange={(e) => setFieldValue(field.slug, e.target.value)}
129
+ />
130
+ </label>
131
+ ))}
132
+ <button type="submit">Request a quote</button>
133
+ </form>
134
+ )
135
+ }
136
+ ```
137
+
138
+ It returns a token such as `given-name`, `email`, `tel`, `organization` or
139
+ `postal-code`, or `undefined` when a field has no confident match, and React
140
+ leaves the attribute off for `undefined`. Only text, email and phone fields and
141
+ selects get a token, so a message box, a number, a date or a checkbox gets
142
+ `undefined`. To stay short, the example draws every field as a text input and
143
+ leaves out error messages and steps; draw each field with the control it calls
144
+ for and pass the same `autoComplete={autocompleteFor(field)}` to all of them.
145
+
146
+ Use it instead of typing tokens per field. It works the token out from the
147
+ field's type, slug and label, so your markup doesn't keep a list of its own that
148
+ can drift. `@sonordev/site-kit/forms` is a client module, like `useForm`, so call
149
+ `autocompleteFor` from a client component (a file that starts with
150
+ `'use client'`). Calling it in a Server Component fails the build.
151
+
152
+ To type a field you pass around, use `UseFormReturn['fields'][number]`, or
153
+ `AutocompleteField` for a helper that only needs the token. Don't use the
154
+ `FormField` this entry exports: that's the shape `formsApi` takes (camelCase keys
155
+ such as `fieldType`), not what `useForm` returns.
156
+
104
157
  ### Option 3: Render Prop
105
158
 
106
159
  ```tsx
@@ -228,7 +281,8 @@ nothing to configure.
228
281
  fields carry the right `autocomplete` token (`given-name`, `email`, `tel`,
229
282
  `organization`, `url`, `postal-code`, ...), read from the field's CRM
230
283
  destination in Sonor, then its type, slug and label. Browser autofill and
231
- password managers use the same tokens.
284
+ password managers use the same tokens. A headless form adds them with
285
+ `autocompleteFor` (see [Autocomplete on your own controls](#autocomplete-on-your-own-controls)).
232
286
  - **Accessible wiring.** Error and help text are tied to their control
233
287
  (`aria-describedby`), an errored control says so (`aria-invalid`), radio and
234
288
  checkbox groups are named by their question, and rating stars by their
@@ -252,7 +306,8 @@ nothing to configure.
252
306
  until the page can catch the click.
253
307
 
254
308
  With `useForm`, spread `toolAttributes` on your `<form>` and use
255
- `handleSubmit` as its `onSubmit` to get the same behavior.
309
+ `handleSubmit` as its `onSubmit` to get the WebMCP behavior and the agent-sent
310
+ tag, and add `autocompleteFor` to your controls for the autofill tokens.
256
311
 
257
312
  `ServerForm`'s `enhance` prop is ignored since 7.2.0. Only the interactive
258
313
  form can send a managed form, so `enhance={false}` only ever produced a form
package/src/mcp/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # `@sonordev/site-kit/mcp` — WebMCP & Model Context Protocol
1
+ # `@sonordev/site-kit/mcp`: WebMCP & Model Context Protocol
2
2
 
3
3
  Make a marketing site something an AI agent can **use**, not just read.
4
4
 
@@ -8,12 +8,12 @@ definitions:
8
8
 
9
9
  | Surface | Who uses it | Entry point |
10
10
  |---|---|---|
11
- | Remote MCP endpoint (Streamable HTTP) | Off-browser agents — Claude, Cursor, any MCP client | `createMcpHandler` |
11
+ | Remote MCP endpoint (Streamable HTTP) | Off-browser agents: Claude, Cursor, any MCP client | `createMcpHandler` |
12
12
  | Server card and experimental AI catalog | Crawlers and clients discovering the endpoint | `createMcpServerCardHandler`, `createMcpAiCatalogHandler` |
13
13
  | In-page WebMCP | Browser-driving agents | `<WebMcpTools>`, `declarativeToolForm` |
14
14
 
15
- One definition feeding all three is the point. The alternative — a tool list for
16
- the endpoint and a separate one for the page — drifts, and a stale tool
15
+ One definition feeding all three is the point. The alternative, a tool list for
16
+ the endpoint and a separate one for the page, drifts, and a stale tool
17
17
  definition is worse than none, because the agent believes it.
18
18
 
19
19
  ---
@@ -36,7 +36,7 @@ site's pages use, so an agent gets what a visitor gets.
36
36
  | `find_pages` | The page that covers a topic |
37
37
  | `list_articles`, `get_article` | Its articles (`articles: false` drops them) |
38
38
  | `get_reviews` | Reviews verbatim, with who wrote them, and the rating |
39
- | `list_offerings` | Priced products, services, events (opt-in: `offerings: { path }`); private prices are left out |
39
+ | `list_offerings` | Priced products, services, events (opt-in: `offerings: { path, read }`, see below); private prices are left out |
40
40
  | `check_availability` | Open appointment times, read only (opt-in: `booking: { path }`) |
41
41
  | `get_inquiry_form`, `send_inquiry` | An inquiry for a person (opt-in: `inquiry: { form }`) |
42
42
 
@@ -63,6 +63,47 @@ export const { POST, GET, DELETE, OPTIONS } = createMcpHandler({
63
63
  })
64
64
  ```
65
65
 
66
+ **`list_offerings` needs a reader (8.0).** The catalog it lists belongs to
67
+ [commerce-kit](https://sonor.dev/commerce-kit), so you hand the tool commerce-kit's
68
+ fetcher instead of site-kit importing it:
69
+
70
+ ```ts
71
+ // lib/mcp.ts
72
+ import 'server-only'
73
+ import { sonorMcpServer } from '@sonordev/site-kit/mcp/sonor'
74
+ import { getOfferingsResult } from '@sonordev/commerce-kit/commerce/server'
75
+
76
+ export const mcpServer = sonorMcpServer({
77
+ businessName: 'Example Studio',
78
+ offerings: { path: '/shop', read: getOfferingsResult },
79
+ })
80
+ ```
81
+
82
+ `read` is required and is `getOfferingsResult` from
83
+ `@sonordev/commerce-kit/commerce/server`, so the site needs commerce-kit installed
84
+ (`npm install @sonordev/commerce-kit`). When a request fails it tells the agent the
85
+ catalog is unavailable; the plain `getOfferings` from the same entry also works, but
86
+ it reads an outage as an empty catalog. The other tools, `list_services` included,
87
+ don't. `path` is where the offerings' pages live: each offering links to
88
+ `<path>/<slug>` on the site, and `path` defaults to `/shop`. An agent can narrow the
89
+ list with `type` (`product`, `service` or `event`), search it with `query`, and cap
90
+ it with `limit` (at most 20). The tool, how it ranks a query, and its rule that a
91
+ price the business keeps private is never shown all stay in site-kit.
92
+
93
+ `read` is typed `McpOfferingsReader`, and what it returns is a list of `McpOffering`s
94
+ (a name, type, slug, descriptions and a price), or `{ ok, data }` around that list. Both
95
+ types are exported from `@sonordev/site-kit/mcp/sonor`. commerce-kit's fetchers satisfy
96
+ them as they are, so you only meet the types if you supply the catalog from somewhere
97
+ else: write a function that takes `({ apiUrl, apiKey, projectId }, { type, limit })` and
98
+ returns the offerings, and answer `{ ok: false, data: [] }` when the request fails, so
99
+ the agent hears "unavailable" instead of "nothing for sale".
100
+
101
+ Without `read`, `sonorMcpServer` throws when the server is built, with a message that
102
+ names the fix. `npx sonor-setup mcp --offerings /shop` writes this wiring for a new
103
+ site, import included, and tells you to install commerce-kit. Before 8.0 the option
104
+ was `offerings: { path }`; see
105
+ [Moving a site to site-kit 8](../../docs/MIGRATING-TO-8.md#the-mcp-change).
106
+
66
107
  **`send_inquiry` has a person behind it.** It refuses unless
67
108
  `person_confirmed` is true (the person asked to be contacted and agreed to
68
109
  share their details), files through Sonor's agent-inquiry door with the
@@ -205,7 +246,7 @@ aliases for clients that already use them. The card handler's default remains
205
246
  ### 4. Register in-page (optional, for browser agents)
206
247
 
207
248
  ```tsx
208
- // app/layout.tsx — a CHILDLESS SIBLING, never a wrapper
249
+ // app/layout.tsx: a CHILDLESS SIBLING, never a wrapper
209
250
  <SiteKitLayout>{children}</SiteKitLayout>
210
251
  <WebMcpTools endpoint="/api/mcp" />
211
252
  ```
@@ -290,13 +331,13 @@ The header and label default to `x-site-mcp-transport` and
290
331
  `{ header, label }` to all three factories (for example
291
332
  `x-example-mcp-transport` / `example-mcp-transport-v1`). This entry is Node
292
333
  only and imports no `server-only`, so the plain-Node function can load it. It
293
- is not re-exported from `@sonordev/site-kit/mcp`, which stays runtime-neutral.
334
+ isn't re-exported from `@sonordev/site-kit/mcp`, which stays runtime-neutral.
294
335
 
295
336
  ---
296
337
 
297
338
  ## Design notes
298
339
 
299
- ### The card does not list tools — on purpose
340
+ ### The card doesn't list tools, on purpose
300
341
 
301
342
  The card's discovery shape omits primitives: what a server exposes can
302
343
  vary with auth state and configuration, so the authoritative list is whatever
@@ -306,8 +347,8 @@ source of truth that goes stale silently.
306
347
  We still publish a **summary** (name + description + read-only flag) under
307
348
  `_meta['io.sonor.site-kit/tools']`. `_meta` is the spec's sanctioned extension
308
349
  point and requires a reverse-DNS prefix, so the hint rides along without
309
- pretending to be standard — useful for crawlers that index the card and never
310
- connect.
350
+ pretending to be standard. That's useful for crawlers that index the card and
351
+ never connect.
311
352
 
312
353
  ### Experimental discovery and compatibility paths
313
354
 
@@ -323,21 +364,21 @@ URLs working while adding the catalog.
323
364
 
324
365
  `dispatch()` answers both protocol eras on one endpoint:
325
366
 
326
- - **Modern (`2026-07-28`)** — stateless, per-request `_meta` carrying the
367
+ - **Modern (`2026-07-28`)**: stateless, per-request `_meta` carrying the
327
368
  protocol version, mirrored into `MCP-Protocol-Version`. `server/discover`
328
369
  replaces the handshake. No sessions, no GET stream (both return `405`).
329
- - **Legacy (`≤ 2025-11-25`)** — the `initialize` handshake, which is what most
370
+ - **Legacy (`≤ 2025-11-25`)**: the `initialize` handshake, which is what most
330
371
  shipped clients and SDKs still speak.
331
372
 
332
373
  Supporting only the current revision would be spec-correct and unusable today.
333
374
 
334
- ### Header mirroring: mismatch is fatal, absence is not
375
+ ### Header mirroring: mismatch is fatal, absence isn't
335
376
 
336
377
  The modern revision mirrors `method` and `params.name` into `Mcp-Method` and
337
378
  `Mcp-Name` so intermediaries can route without parsing bodies, and requires
338
379
  servers to reject disagreements (`-32020`).
339
380
 
340
- We always reject a **mismatch** — that is the real security property, stopping
381
+ We always reject a **mismatch**; that's the real security property, stopping
341
382
  a load balancer and the server from acting on different values. A merely
342
383
  **absent** header is tolerated unless you set `strictHeaders: true`, because a
343
384
  public marketing endpoint exists to be reachable and today's clients frequently
@@ -360,13 +401,13 @@ one or the other makes you invisible to a large slice of the ecosystem.
360
401
 
361
402
  `<WebMcpTools>` does nothing at all unless `document.modelContext` exists. That
362
403
  gate comes before the `tools/list` fetch, so the cost for a human visitor is a
363
- single property check — not a request, and not a byte of page weight.
404
+ single property check: not a request, and not a byte of page weight.
364
405
 
365
406
  ### In-page tools are proxied, not re-implemented
366
407
 
367
408
  `<WebMcpTools>` registers thin wrappers that POST `tools/call` to this site's
368
409
  own endpoint, so the in-page tool and the remote tool run the same server-side
369
- handler. It also keeps `SONOR_API_KEY` out of the client bundle — registering
410
+ handler. It also keeps `SONOR_API_KEY` out of the client bundle: registering
370
411
  real handlers client-side would pull server code, and the key with it, into a
371
412
  `'use client'` graph and a public chunk.
372
413
 
@@ -376,13 +417,13 @@ Tools that genuinely need live DOM state go in `localTools` and run in-page.
376
417
 
377
418
  `<WebMcpTools>` waits for the page to go quiet (`useDeferredActivation`) before
378
419
  touching `document.modelContext`. Agents poll or listen for `toolchange`, so a
379
- few hundred milliseconds costs nothing — a blocked LCP costs a lot.
420
+ few hundred milliseconds costs nothing, while a blocked LCP costs a lot.
380
421
 
381
422
  ---
382
423
 
383
424
  ## Writing good tools
384
425
 
385
- The `description` is the highest-leverage field in this module. It is the only
426
+ The `description` is the highest-leverage field in this module. It's the only
386
427
  thing a model reads when deciding whether to call the tool.
387
428
 
388
429
  - **Verb-led `snake_case` names**: `get_services`, `request_site_audit`.
@@ -391,10 +432,10 @@ thing a model reads when deciding whether to call the tool.
391
432
  - **Set `annotations` honestly.** `readOnlyHint` on lookups; `destructiveHint`
392
433
  on anything creating a record. Good agents use these to decide what needs a
393
434
  human.
394
- - **Never `toolautosubmit` a lead form.** That is how an agent files fifty
435
+ - **Never `toolautosubmit` a lead form.** That's how an agent files fifty
395
436
  audit requests by accident.
396
437
  - **Return the caveat with the data.** A pricing tool should return the ranges
397
- *and* the fact that they are ranges — otherwise the model quotes a number as
438
+ *and* the fact that they're ranges; otherwise the model quotes a number as
398
439
  a commitment.
399
440
 
400
441
  ## Testing an endpoint by hand