@sonordev/site-kit 7.0.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +3606 -0
  2. package/README.md +12 -13
  3. package/agent-manifest.json +11 -5
  4. package/dist/{AnalyticsProvider-EMM2TKRE.js → AnalyticsProvider-ZMQUV33M.js} +4 -4
  5. package/dist/{ArticleViewTracker-RA64BGL6.js → ArticleViewTracker-V7NUXYBY.js} +3 -3
  6. package/dist/{BlocksPopup-D25RFNOV.js → BlocksPopup-52EU7OUY.js} +4 -4
  7. package/dist/{ChatWidget-RYI7BMJJ.js → ChatWidget-5BHMNR57.js} +5 -5
  8. package/dist/{EngageWidget-UKFCN33M.js → EngageWidget-PCGLX7SO.js} +4 -4
  9. package/dist/{FileField-MUHA7LZR.js → FileField-TSFGNAMY.js} +3 -3
  10. package/dist/{FormSpotlight-TCLPWPLL.js → FormSpotlight-XLBWEOTE.js} +1 -1
  11. package/dist/{FormStage-CNYLP6I6.js → FormStage-IJQ5Q2X6.js} +1 -1
  12. package/dist/{ManagedForm-7ZL5SKTO.js → ManagedForm-Z3PKOPIZ.js} +6 -6
  13. package/dist/{ManagedNewsletterForm-33B4JLX7.js → ManagedNewsletterForm-QAO3POLC.js} +4 -4
  14. package/dist/{SignalCore-L5FVDHFE.js → SignalCore-RBA3VDBL.js} +3 -3
  15. package/dist/{SiteDesignReporter-4JOFL4FP.js → SiteDesignReporter-C4LR5X2V.js} +5 -5
  16. package/dist/SitemapSync-XVMGKCF3.js +8 -0
  17. package/dist/_client/booking-widget.js +5 -5
  18. package/dist/affiliates/index.js +3 -3
  19. package/dist/analytics/index.js +4 -4
  20. package/dist/articles/index.js +1 -1
  21. package/dist/articles/server-ui.js +1 -1
  22. package/dist/chat/index.js +5 -5
  23. package/dist/{chunk-FYBZ5SNP.js → chunk-3G2SE2J4.js} +1 -1
  24. package/dist/{chunk-HGCK465A.js → chunk-3J2ERO3I.js} +1 -1
  25. package/dist/{chunk-KXPBMCFL.js → chunk-3QI26673.js} +3 -1
  26. package/dist/{chunk-BMO3VGMR.js → chunk-3XPJKZ6D.js} +30 -7
  27. package/dist/{chunk-QGHSMJKW.js → chunk-4JQQDCMO.js} +1 -1
  28. package/dist/{chunk-6HDT4G4A.js → chunk-4YTYGG2C.js} +2 -2
  29. package/dist/{chunk-N2UVOR3X.js → chunk-662ILEZ6.js} +2 -0
  30. package/dist/chunk-6G43IRWR.js +4 -0
  31. package/dist/{chunk-LVESVYCE.js → chunk-7MHHWZKC.js} +11 -117
  32. package/dist/{chunk-MV2MBTC3.js → chunk-7QTMMHUO.js} +1 -1
  33. package/dist/{chunk-KPAZG65P.js → chunk-CGWUXUYZ.js} +138 -46
  34. package/dist/{chunk-4IQ52CXL.js → chunk-DUAO4Q75.js} +2 -2
  35. package/dist/{chunk-4RMVXRBO.js → chunk-EIULXXUJ.js} +3 -3
  36. package/dist/{chunk-3KUUH2YP.js → chunk-EVFZ7KEW.js} +1 -1
  37. package/dist/{chunk-OFOAHPUV.js → chunk-F42R35NV.js} +1 -1
  38. package/dist/{chunk-QANVUXKH.js → chunk-FLR3EMK6.js} +1 -1
  39. package/dist/{chunk-P4GRY6QP.js → chunk-GIAOPEN6.js} +1 -1
  40. package/dist/{chunk-P5J7VMQ3.js → chunk-GWUKQ26F.js} +1 -1
  41. package/dist/{chunk-TT63HHIT.js → chunk-HAG4YIZY.js} +1 -1
  42. package/dist/{chunk-QZZIKMAT.js → chunk-HVH37YPX.js} +1 -1
  43. package/dist/{chunk-SSUQKA7L.js → chunk-J4D6ZXRW.js} +1 -1
  44. package/dist/{chunk-GYESATRY.js → chunk-L2DJD5Y4.js} +1 -1
  45. package/dist/{chunk-WATH55UY.js → chunk-LFXVE32I.js} +1 -1
  46. package/dist/chunk-LPH5FANE.js +169 -0
  47. package/dist/{chunk-V6LSQRTH.js → chunk-PLUP2KN5.js} +1 -1
  48. package/dist/chunk-RYVDGXC2.js +19 -0
  49. package/dist/{chunk-EGOD74PP.js → chunk-U35H2JIQ.js} +2 -2
  50. package/dist/chunk-VCJYLYJV.js +49 -0
  51. package/dist/{chunk-FL4EPUWA.js → chunk-W2CL2DB3.js} +2 -2
  52. package/dist/{chunk-UZN4ZYR2.js → chunk-XD3ZQET6.js} +1 -1
  53. package/dist/{chunk-CVTVNC2U.js → chunk-XNVSCQ2O.js} +2 -2
  54. package/dist/{chunk-T3MC4HOD.js → chunk-YLSEB32F.js} +1 -1
  55. package/dist/chunk-ZETJTCMV.js +118 -0
  56. package/dist/{chunk-5SEM2V4A.js → chunk-ZIMFQWGJ.js} +3 -3
  57. package/dist/client/index.js +3 -3
  58. package/dist/cms/CmsPage.d.ts +1 -0
  59. package/dist/cms/CmsPreview.d.ts +1 -0
  60. package/dist/cms/CmsSection.d.ts +1 -0
  61. package/dist/cms/index.d.ts +6 -0
  62. package/dist/cms/server-api.d.ts +3 -0
  63. package/dist/commerce/index.js +4 -4
  64. package/dist/config/index.js +1 -1
  65. package/dist/contracts/entries.d.ts +1 -1
  66. package/dist/contracts/site-cache.d.ts +55 -0
  67. package/dist/contracts/site-edit-param.d.ts +7 -0
  68. package/dist/contracts/site-edit.d.ts +77 -0
  69. package/dist/contracts/slot-content.d.ts +111 -0
  70. package/dist/contracts/slots.d.ts +39 -25
  71. package/dist/engage/index.js +6 -6
  72. package/dist/fleet/index.js +4 -4
  73. package/dist/forms/index.js +8 -8
  74. package/dist/forms/server.js +2 -2
  75. package/dist/forms/types.d.ts +3 -1
  76. package/dist/images/index.js +4 -4
  77. package/dist/index.js +1 -1
  78. package/dist/layout/client.js +8 -7
  79. package/dist/layout/index.js +9 -8
  80. package/dist/llms/index.js +4 -2
  81. package/dist/llms/seo-revalidate.d.ts +8 -1
  82. package/dist/maps/index.js +3 -3
  83. package/dist/mcp/sonor.js +6 -6
  84. package/dist/overlay-RXV6U6QC.js +353 -0
  85. package/dist/proxy/index.js +2 -2
  86. package/dist/proxy/securityHeaders.d.ts +4 -0
  87. package/dist/revalidate/index.d.ts +44 -0
  88. package/dist/revalidate/index.js +27 -0
  89. package/dist/seo/ManagedContent.d.ts +2 -0
  90. package/dist/seo/client.js +4 -4
  91. package/dist/seo/index.js +9 -8
  92. package/dist/seo/llms.js +4 -2
  93. package/dist/seo/register-sitemap-cli.js +1 -1
  94. package/dist/seo/server.js +3 -2
  95. package/dist/seo/sitemap.js +2 -2
  96. package/dist/server/index.js +2 -2
  97. package/dist/{server-api-GJJQZVG7.js → server-api-BVCBLJKL.js} +2 -1
  98. package/dist/shared/build-entries.d.ts +1 -0
  99. package/dist/shared/edit-bridge.d.ts +8 -0
  100. package/dist/shared/version.d.ts +1 -1
  101. package/dist/signal/index.js +2 -2
  102. package/dist/sitemap/index.js +2 -2
  103. package/dist/slots/ManagedLink.d.ts +31 -0
  104. package/dist/slots/ManagedList.d.ts +30 -0
  105. package/dist/slots/ManagedRichText.d.ts +31 -0
  106. package/dist/slots/contract.js +2 -1
  107. package/dist/slots/edit/locate.d.ts +30 -0
  108. package/dist/slots/edit/overlay.d.ts +18 -0
  109. package/dist/slots/index.d.ts +12 -4
  110. package/dist/slots/index.js +4 -2
  111. package/dist/slots/revalidate.d.ts +8 -3
  112. package/dist/slots/rich.d.ts +7 -0
  113. package/dist/slots/server-api.d.ts +6 -2
  114. package/dist/sync/index.js +5 -5
  115. package/dist/website/images.js +4 -4
  116. package/dist/website/index.js +5 -5
  117. package/dist/website/popups.js +4 -4
  118. package/dist/website/slots/contract.js +2 -1
  119. package/dist/website/slots.js +4 -2
  120. package/dist/{writeLLMsTxt-UMHKGNRR.js → writeLLMsTxt-QR23OQUE.js} +1 -1
  121. package/docs/MIGRATING-TO-7.md +146 -0
  122. package/docs.json +69 -0
  123. package/package.json +14 -4
  124. package/src/admin-auth/README.md +88 -0
  125. package/src/analytics/README.md +264 -0
  126. package/src/articles/README.md +325 -0
  127. package/src/commerce/README.md +109 -0
  128. package/src/cta-bar/README.md +154 -0
  129. package/src/engage/README.md +241 -0
  130. package/src/forms/README.md +219 -0
  131. package/src/images/README.md +74 -0
  132. package/src/layout/README.md +66 -0
  133. package/src/llms/README.md +723 -0
  134. package/src/mcp/README.md +376 -0
  135. package/src/motion/README.md +372 -0
  136. package/src/og/README.md +304 -0
  137. package/src/proxy/README.md +152 -0
  138. package/src/redirects/README.md +74 -0
  139. package/src/reputation/README.md +64 -0
  140. package/src/revalidate/README.md +82 -0
  141. package/src/seo/README.md +346 -0
  142. package/src/signal/README.md +115 -0
  143. package/src/sitemap/README.md +127 -0
  144. package/src/slots/README.md +168 -0
  145. package/src/sync/README.md +115 -0
  146. package/dist/SitemapSync-7WKY4HXI.js +0 -8
  147. package/dist/chunk-SS636UDN.js +0 -35
@@ -0,0 +1,219 @@
1
+ # Forms — `@sonordev/site-kit/forms`
2
+
3
+ Sonor-managed forms with multi-step support, conditional logic, validation, anti-bot protection, and automatic CRM routing.
4
+
5
+ ## Usage
6
+
7
+ ### Option 1: Managed (simplest)
8
+
9
+ ```tsx
10
+ import { ManagedForm } from '@sonordev/site-kit/forms'
11
+
12
+ export default function ContactPage() {
13
+ return <ManagedForm formId="contact-form" />
14
+ }
15
+ ```
16
+
17
+ Fetches form config from Sonor, renders fields, handles submission, routes to CRM.
18
+
19
+ ## Experiences (4.0)
20
+
21
+ Since 4.0 that one line gets you the **spotlight** experience by default: the
22
+ familiar layout, alive. A glowing ring visits the field you're in, completed
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
25
+ an edit control to reopen it.
26
+
27
+ | Experience | What it is | How to get it |
28
+ |---|---|---|
29
+ | `spotlight` | **Default.** Fields perform in place; answered rows become sentences | nothing to do |
30
+ | `stage` | Accordion: one beat at a time on a single surface, answered beats settle into filled rows | `experience="stage"` |
31
+ | `classic` | The plain grid form, no choreography | `experience="classic"` |
32
+
33
+ ```tsx
34
+ <ManagedForm formId="contact-form" /> // spotlight
35
+ <ManagedForm formId="contact-form" experience="stage" /> // accordion
36
+ <ManagedForm formId="contact-form" experience="classic" /> // opt out
37
+ ```
38
+
39
+ Sonor can decide instead of the site, with no deploy on either side — set the
40
+ form's `layout` to `classic`, `stage`, or `spotlight`. An explicit
41
+ `experience` prop always wins over the config.
42
+
43
+ Three things hold for every experience:
44
+
45
+ - **The lead is always capturable.** SSR and no-JS render the plain classic
46
+ form; the experience layers on after hydration. If its chunk never arrives,
47
+ the form still works and still submits.
48
+ - **Nobody pays for what they don't use.** Experience code is loaded on
49
+ demand, so a `classic` form downloads none of it.
50
+ - **Same engine.** Identical validation, honeypot, reCAPTCHA, and CRM routing
51
+ — an experience is a rendering, not a fork.
52
+
53
+ ### Editing what the form says back
54
+
55
+ The sentence for a row comes from the kit's built-in wording unless you write
56
+ your own in Sonor: set **`completion_message`** on a field (the first answered
57
+ field in the row that has one wins). Tokens:
58
+
59
+ | Token | Resolves to |
60
+ |---|---|
61
+ | `{value}` | this field's answer |
62
+ | `{label}` | this field's label |
63
+ | `{first_name}` | the form's first-name answer, if any |
64
+ | `{any_field_slug}` | that field's answer |
65
+
66
+ ```
67
+ Welcome aboard, {first_name}. We'll reach you at {email}.
68
+ ```
69
+
70
+ Leave it empty to keep the built-in sentence. Unknown or unanswered tokens
71
+ render as nothing — never raw braces.
72
+
73
+ ### Option 2: Headless Hook (full UI control)
74
+
75
+ ```tsx
76
+ 'use client'
77
+ import { useForm } from '@sonordev/site-kit/forms'
78
+
79
+ export function ContactForm() {
80
+ const {
81
+ fields, values, errors, setFieldValue, submit, isSubmitting,
82
+ step, totalSteps, isMultiStep, nextStep, prevStep, isLastStep,
83
+ } = useForm('contact-form')
84
+
85
+ return (
86
+ <form onSubmit={(e) => { e.preventDefault(); submit() }}>
87
+ {fields.map(field => (
88
+ <div key={field.slug}>
89
+ <label>{field.label}</label>
90
+ <input
91
+ value={String(values[field.slug] || '')}
92
+ onChange={(e) => setFieldValue(field.slug, e.target.value)}
93
+ />
94
+ {errors[field.slug] && <span className="error">{errors[field.slug]}</span>}
95
+ </div>
96
+ ))}
97
+ {isMultiStep && !isLastStep && <button type="button" onClick={nextStep}>Next</button>}
98
+ {isLastStep && <button type="submit" disabled={isSubmitting}>Submit</button>}
99
+ </form>
100
+ )
101
+ }
102
+ ```
103
+
104
+ ### Option 3: Render Prop
105
+
106
+ ```tsx
107
+ <ManagedForm formId="contact-form">
108
+ {({ fields, values, setFieldValue, submit }) => (
109
+ <MyCustomFormUI fields={fields} values={values} onChange={setFieldValue} onSubmit={submit} />
110
+ )}
111
+ </ManagedForm>
112
+ ```
113
+
114
+ ### Option 4: Programmatic API
115
+
116
+ ```ts
117
+ import { formsApi, field, configureFormsApi } from '@sonordev/site-kit/forms'
118
+
119
+ configureFormsApi({ baseUrl: 'https://api.sonor.io', apiKey: 'sonor_...' })
120
+
121
+ const form = await formsApi.create({
122
+ projectId: 'xxx',
123
+ slug: 'newsletter',
124
+ name: 'Newsletter Signup',
125
+ formType: 'newsletter',
126
+ fields: [
127
+ field.email('email', 'Email', { isRequired: true }),
128
+ field.text('name', 'Name'),
129
+ ],
130
+ })
131
+ ```
132
+
133
+ ## ManagedForm Props
134
+
135
+ ```ts
136
+ interface ManagedFormProps {
137
+ formId: string // Form slug or ID
138
+ className?: string
139
+ onSuccess?: (submission: FormSubmitResult) => void
140
+ onError?: (error: Error) => void
141
+ children?: (renderProps: UseFormReturn) => ReactNode // Render prop override
142
+ }
143
+ ```
144
+
145
+ ## useForm Return
146
+
147
+ ```ts
148
+ interface UseFormReturn {
149
+ form: ManagedFormConfig | null
150
+ isLoading: boolean
151
+ fetchError: Error | null
152
+ allFields: FormField[]
153
+ fields: FormField[] // Current step's fields
154
+ visibleFields: FormField[] // After conditional logic
155
+ values: Record<string, unknown>
156
+ errors: Record<string, string>
157
+ setFieldValue: (key: string, value: unknown) => void
158
+ step: number
159
+ totalSteps: number
160
+ isMultiStep: boolean
161
+ progress: number // 0-100
162
+ nextStep: () => void
163
+ prevStep: () => void
164
+ goToStep: (n: number) => void
165
+ canGoNext: boolean
166
+ canGoPrev: boolean
167
+ isLastStep: boolean
168
+ validate: () => boolean
169
+ submit: () => Promise<void>
170
+ isSubmitting: boolean
171
+ isComplete: boolean
172
+ reset: () => void
173
+ }
174
+ ```
175
+
176
+ ## Field Types
177
+
178
+ `text`, `email`, `phone`, `tel`, `number`, `textarea`, `select`, `multi-select`, `checkbox`, `radio`, `date`, `time`, `datetime`, `file`, `signature`, `rating`, `slider`, `hidden`, `heading`, `section_header`, `paragraph`
179
+
180
+ ## Field Builder
181
+
182
+ ```ts
183
+ field.text(slug, label, options?)
184
+ field.email(slug, label, options?)
185
+ field.phone(slug, label, options?)
186
+ field.textarea(slug, label, options?)
187
+ field.select(slug, label, { options: [{ value, label }] })
188
+ field.date(slug, label, options?)
189
+ field.checkbox(slug, label, options?)
190
+ field.rating(slug, label, options?)
191
+ ```
192
+
193
+ ## Form Routing
194
+
195
+ Submissions auto-route based on `form_type`:
196
+
197
+ | Form Type | Routes To | Use Case |
198
+ |-----------|-----------|----------|
199
+ | `prospect` / `lead-capture` / `contact` | CRM Contacts | Sales inquiries, quotes |
200
+ | `support` | Support Tickets | Help requests |
201
+ | `feedback` | Feedback entries | User feedback |
202
+ | `newsletter` | Email Subscribers | Newsletter signups |
203
+ | `custom` | Form Submissions only | Custom handling |
204
+
205
+ ## Anti-Bot Protection
206
+
207
+ - **Honeypot fields** — hidden fields that bots fill (server-side rejection)
208
+ - **reCAPTCHA Enterprise** — optional Google reCAPTCHA v3 scoring
209
+ - **Submission timing** — forms that submit in < 3 seconds are flagged
210
+ - **Composite spam scoring** — name patterns, email domain, user-agent, IP rate, message content
211
+ - **NestJS ThrottlerGuard** — 10 requests/minute per IP at HTTP level
212
+
213
+ ## Styles
214
+
215
+ ```tsx
216
+ import '@sonordev/site-kit/forms/styles.css' // Optional default styles
217
+ ```
218
+
219
+ All components use `.sk-form__*` class names. Override with `className` prop or CSS variables.
@@ -0,0 +1,74 @@
1
+ # Images — `@sonordev/site-kit/images`
2
+
3
+ Sonor-managed image slots with dev-mode editing, focal point support, and image picker.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ import { ManagedImage } from '@sonordev/site-kit/images'
9
+
10
+ export default function HeroSection() {
11
+ return (
12
+ <ManagedImage
13
+ slotId="hero-background"
14
+ alt="Hero background"
15
+ width={1920}
16
+ height={1080}
17
+ objectFit="cover"
18
+ fallback="/images/default-hero.jpg"
19
+ />
20
+ )
21
+ }
22
+ ```
23
+
24
+ ## Props
25
+
26
+ ```ts
27
+ interface ManagedImageProps {
28
+ slotId: string // Unique slot identifier (e.g., 'hero-background')
29
+ pagePath?: string // Page-specific slots (defaults to current path)
30
+ alt?: string // Fallback alt text
31
+ className?: string
32
+ width?: number | string
33
+ height?: number | string
34
+ objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
35
+ fallback?: string // Fallback image URL when slot is empty
36
+ placeholder?: React.ReactNode // Custom placeholder component
37
+ onLoad?: () => void
38
+ onError?: (error: Error) => void
39
+ priority?: boolean // Eager loading (Next.js Image priority)
40
+ style?: React.CSSProperties
41
+ forceDevMode?: boolean // Enable dev picker outside localhost
42
+ }
43
+ ```
44
+
45
+ ## Dev Mode
46
+
47
+ Dev mode is on when `NODE_ENV` is `development`, on `localhost` / `127.0.0.1`, on any host with `?sonor_dev=true` in the URL, or with `forceDevMode`:
48
+ - Click any `ManagedImage` to open the image picker modal
49
+ - Browse files by folder, search, upload new images
50
+ - Set alt text and focal point
51
+ - Shows slot ID in empty states for debugging
52
+
53
+ ## Focal Point
54
+
55
+ When an image has `focal_point_x` / `focal_point_y` set in Sonor, the component auto-calculates `object-position` so the focal area stays visible during cropping.
56
+
57
+ ## API Functions
58
+
59
+ ```ts
60
+ import { fetchManagedImage, uploadImage, assignImageToSlot } from '@sonordev/site-kit/images'
61
+
62
+ const image = await fetchManagedImage(slotId, pagePath)
63
+ const uploaded = await uploadImage(file, { folder: 'heroes', category: 'background' })
64
+ await assignImageToSlot(slotId, uploaded.id, pagePath)
65
+ ```
66
+
67
+ ## Favicon
68
+
69
+ ```tsx
70
+ import { ManagedFavicon } from '@sonordev/site-kit/images'
71
+
72
+ // In layout <head>:
73
+ <ManagedFavicon /> // Fetches Sonor logo as favicon link tags
74
+ ```
@@ -0,0 +1,66 @@
1
+ # Layout — `@sonordev/site-kit/layout`
2
+
3
+ RSC-compatible master layout that auto-composes all site-kit features.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ // app/layout.tsx
9
+ import { SiteKitLayout } from '@sonordev/site-kit/layout'
10
+
11
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
12
+ return (
13
+ <html lang="en">
14
+ <body>
15
+ <SiteKitLayout>{children}</SiteKitLayout>
16
+ </body>
17
+ </html>
18
+ )
19
+ }
20
+ ```
21
+
22
+ Zero-config: reads `SONOR_API_KEY` from env, injects it into client modules automatically.
23
+
24
+ ## Props
25
+
26
+ ```ts
27
+ interface SiteKitLayoutProps {
28
+ children: React.ReactNode
29
+ apiKey?: string // Defaults to SONOR_API_KEY env var
30
+ apiUrl?: string // Defaults to SONOR_API_URL, then https://api.sonor.io
31
+ projectId?: string // For Engage chat routing (auto-resolved if omitted)
32
+ analytics?: boolean | AnalyticsConfig // Default: true
33
+ engage?: boolean | EngageConfig // Default: true
34
+ signal?: boolean | SignalConfig // Default: false
35
+ sitemapSync?: boolean // Default: false (build-time + server reconciler own this)
36
+ fleet?: boolean // Default: true (once-per-session kit version heartbeat)
37
+ defer?: boolean // Default: true (client modules wait for load + idle)
38
+ favicon?: boolean // Default: true
39
+ managedScripts?: boolean // Default: true
40
+ debug?: boolean // Default: false
41
+ showLlmsTxtFooterLink?: boolean // Default: false (prefer middleware discovery headers)
42
+ speculation?: boolean | { mode?: 'prerender' | 'prefetch'; exclude?: string[] } // Default: false
43
+ }
44
+ ```
45
+
46
+ Module options live with each module: [Analytics](../analytics/README.md) (`trackPageViews`, `excludePaths`, `site`, `allowInFrame`, `allowLocalhost`) and [Engage](../engage/README.md) (`position`, `chatEnabled`, and launcher placement).
47
+
48
+ ## What It Composes
49
+
50
+ **Server-side (RSC):**
51
+ - `ManagedFavicon` — Sonor logo as `<link>` tags
52
+ - `ManagedScripts` — tracking pixels/analytics tags in `<head>` and body-end positions
53
+ - API preconnect/dns-prefetch hints
54
+
55
+ **Client-side (lazy-loaded island):**
56
+ - `AnalyticsProvider` — page views, scroll depth, heatmap clicks, Web Vitals
57
+ - `EngageWidget` — popups, nudges, chat
58
+ - `SignalBridge` — A/B experiments, behavior tracking (opt-in)
59
+ - `SitemapSync` — parses `/sitemap.xml` and syncs to Sonor (opt-in)
60
+ - `FleetHeartbeat` — reports the kit version and enabled modules once per session
61
+
62
+ Since 4.0.0 none of these wrap your page. `{children}` renders first and every module mounts after it as a childless sibling, so `SiteKitLayout` never pushes a route to client rendering. Analytics, Engage, SitemapSync and the heartbeat also wait for window load + idle (or the first interaction) unless you pass `defer={false}`. `SignalBridge` isn't deferred, so experiment variants apply early. Visitor and session IDs come from a shared storage singleton rather than a provider.
63
+
64
+ ## Note
65
+
66
+ `SiteKitProvider` was **removed in 4.0.0**. It wrapped the whole tree client-side, which broke RSC. Migrate an older layout with `npx sonor-setup codemod --only provider-to-layout --write`.