@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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +60 -0
- package/README.md +9 -8
- package/agent-manifest.json +17 -7
- package/dist/{AnalyticsProvider-ZB33W6MC.js → AnalyticsProvider-DJQA66AG.js} +4 -4
- package/dist/{ArticleViewTracker-GMHYLLLZ.js → ArticleViewTracker-GFOAXFV7.js} +3 -3
- package/dist/{BlocksPopup-EA6LYL25.js → BlocksPopup-AHDU34TL.js} +5 -5
- package/dist/ChatWidget-FHBAHFZX.js +17 -0
- package/dist/{FileField-QKA3JB2Q.js → FileField-J5W5MIAZ.js} +3 -3
- package/dist/{FormSpotlight-AQTIA5TP.js → FormSpotlight-PE5YZBFZ.js} +20 -6
- package/dist/{FormStage-FDTLTP3B.js → FormStage-OOPQC27J.js} +27 -7
- package/dist/ManagedForm-JRZAJ7SZ.js +16 -0
- package/dist/{ManagedNewsletterForm-J6QSAGGP.js → ManagedNewsletterForm-TC5542ZG.js} +7 -5
- package/dist/{SignalCore-JZNXOLNT.js → SignalCore-2YP5FCAS.js} +3 -3
- package/dist/SiteChat-EGEZJJVV.js +5 -0
- package/dist/{SiteDesignReporter-W4EJUSTE.js → SiteDesignReporter-YEEWJKWN.js} +5 -5
- package/dist/SitePopups-LQBIXIFV.js +10 -0
- package/dist/SitemapSync-LSZ2EYOF.js +8 -0
- package/dist/_client/booking-widget.js +5 -5
- package/dist/affiliates/index.js +3 -3
- package/dist/analytics/index.js +4 -4
- package/dist/analytics/send-gate.d.ts +1 -1
- package/dist/articles/index.js +2 -2
- package/dist/articles/server-ui.js +3 -2
- package/dist/articles/server.js +2 -1
- package/dist/{engage → chat}/ChatWidget.d.ts +7 -6
- package/dist/{engage → chat}/EchoUiActions.d.ts +1 -1
- package/dist/chat/SiteChat.d.ts +25 -0
- package/dist/{engage → chat}/brand-color.d.ts +1 -1
- package/dist/{engage → chat}/chat-messages.d.ts +1 -1
- package/dist/{engage → chat}/echo-config.d.ts +1 -1
- package/dist/chat/index.d.ts +10 -6
- package/dist/chat/index.js +14 -8
- package/dist/{engage → chat}/launcher-placement.d.ts +1 -7
- package/dist/{engage → chat}/socket-loader.d.ts +2 -2
- package/dist/chat/types.d.ts +136 -0
- package/dist/chunk-27FISYK4.js +4 -0
- package/dist/{chunk-HF57LG73.js → chunk-2AD3CDRR.js} +43 -29
- package/dist/{chunk-M3YEPKP7.js → chunk-2ECWEUHP.js} +1 -1
- package/dist/{chunk-LUXVWITO.js → chunk-4NTBQNHA.js} +247 -78
- package/dist/{chunk-AH5Q262S.js → chunk-56KNHB4U.js} +1 -1
- package/dist/{chunk-GLHQ3LRO.js → chunk-6U3VLV2C.js} +1 -1
- package/dist/{chunk-3SDOQUUF.js → chunk-BEBR4OJD.js} +2 -2
- package/dist/{chunk-JSZN6LDZ.js → chunk-BF7TZYC3.js} +38 -31
- package/dist/{chunk-AQSNPZY4.js → chunk-C2FZBUSS.js} +80 -1
- package/dist/{chunk-PHYMNUJU.js → chunk-CYUFBKJQ.js} +1 -1
- package/dist/{chunk-6WIQCXOO.js → chunk-DNPNYWVL.js} +1 -1
- package/dist/{chunk-V6FDQLR7.js → chunk-EPT6FJUY.js} +53 -21
- package/dist/chunk-HODO5BX5.js +28 -0
- package/dist/chunk-HW7B43E5.js +14 -0
- package/dist/{chunk-PGMVL4AB.js → chunk-I2YX3HVD.js} +2 -2
- package/dist/{chunk-JTU2GOM4.js → chunk-L7U23XI2.js} +18 -6
- package/dist/{chunk-YEMPUQVU.js → chunk-MKMD2HJW.js} +2 -2
- package/dist/{chunk-Q4W23JYM.js → chunk-MRQ2U4LZ.js} +1 -1
- package/dist/chunk-MVR4FY5R.js +187 -0
- package/dist/{chunk-VGH4MDTU.js → chunk-NI2XNEMR.js} +1 -1
- package/dist/{chunk-4Y5FWDPM.js → chunk-NZ4RXN3G.js} +55 -7
- package/dist/{chunk-RHNWA34F.js → chunk-O5RGXTWU.js} +41 -7
- package/dist/{chunk-TXBAOEKG.js → chunk-PKGN32AU.js} +5 -4
- package/dist/chunk-Q6E4OPGG.js +40 -0
- package/dist/chunk-RDPW4D33.js +45 -0
- package/dist/{chunk-UXOGCKL6.js → chunk-T4DLETKR.js} +1 -1
- package/dist/{chunk-NK4CSJNP.js → chunk-UPRSH6Q4.js} +2 -2
- package/dist/{chunk-ERXXITG4.js → chunk-VF4F5WXZ.js} +1 -1
- package/dist/chunk-VWWITFWM.js +300 -0
- package/dist/{chunk-WWSHFRDL.js → chunk-WDQB4OXY.js} +7 -3
- package/dist/{chunk-VDF5DFWT.js → chunk-WGG6GTKW.js} +1 -1
- package/dist/{chunk-CSTQDNGK.js → chunk-WGIDC6QP.js} +43 -24
- package/dist/{chunk-V3PUVBME.js → chunk-XLSQTTUE.js} +2 -4
- package/dist/{chunk-JBE46QJZ.js → chunk-Y6LP36UW.js} +1 -1
- package/dist/chunk-YJLS6JBI.js +370 -0
- package/dist/{chunk-QEDHRBAT.js → chunk-Z65DHMTW.js} +26 -9
- package/dist/{chunk-ILU5I6RW.js → chunk-ZHS2XFLV.js} +1 -1
- package/dist/{chunk-D5EC5KVO.js → chunk-ZKHI4E2B.js} +2 -2
- package/dist/{chunk-CIG5UL2S.js → chunk-ZTLMPUO6.js} +3 -2
- package/dist/client/index.js +3 -3
- package/dist/commerce/index.js +4 -4
- package/dist/contracts/color.d.ts +1 -1
- package/dist/contracts/entries.d.ts +1 -1
- package/dist/contracts/error-page.d.ts +183 -0
- package/dist/contracts/llms.d.ts +1 -0
- package/dist/contracts/proposal-sitemap.d.ts +130 -0
- package/dist/contracts/schema-placeholders.d.ts +87 -0
- package/dist/engage/EngageWidget.d.ts +13 -12
- package/dist/engage/index.d.ts +10 -9
- package/dist/engage/index.js +31 -35
- package/dist/engage/types.d.ts +19 -244
- package/dist/fleet/FleetHeartbeat.d.ts +1 -1
- package/dist/fleet/index.js +4 -4
- package/dist/forms/FormEnhancer.d.ts +25 -12
- package/dist/forms/ServerForm.d.ts +11 -10
- package/dist/forms/StaticForm.d.ts +3 -1
- package/dist/forms/field-autocomplete.d.ts +28 -0
- package/dist/forms/form-dom-values.d.ts +42 -0
- package/dist/forms/index.js +11 -9
- package/dist/forms/server.js +6 -4
- package/dist/forms/static.js +3 -2
- package/dist/forms/submitForm.d.ts +8 -1
- package/dist/forms/types.d.ts +41 -2
- package/dist/forms/useForm.d.ts +21 -4
- package/dist/forms/webmcp.d.ts +38 -0
- package/dist/images/index.js +4 -4
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/dist/layout/SiteKitClientProviders.d.ts +3 -3
- package/dist/layout/SiteKitLayout.d.ts +4 -4
- package/dist/layout/client.d.ts +3 -3
- package/dist/layout/client.js +7 -7
- package/dist/layout/index.js +8 -8
- package/dist/layout/types.d.ts +5 -4
- package/dist/llms/agent-access.d.ts +4 -0
- package/dist/llms/contract.js +1 -1
- package/dist/llms/index.js +6 -6
- package/dist/maps/index.js +3 -3
- package/dist/mcp/WebMcpTools.d.ts +5 -4
- package/dist/mcp/client.js +14 -7
- package/dist/mcp/discovery.d.ts +48 -0
- package/dist/mcp/handlers.d.ts +13 -7
- package/dist/mcp/index.d.ts +5 -3
- package/dist/mcp/index.js +62 -30
- package/dist/mcp/serverCard.d.ts +6 -7
- package/dist/mcp/sonor.js +14 -11
- package/dist/revalidate/index.js +2 -2
- package/dist/seo/client.js +4 -4
- package/dist/seo/index.js +15 -8
- package/dist/seo/llms/contract.js +1 -1
- package/dist/seo/llms.js +6 -6
- package/dist/seo/register-sitemap-cli.js +1 -1
- package/dist/seo/sitemap.js +4 -4
- package/dist/server/index.js +2 -2
- package/dist/shared/dialog.d.ts +27 -0
- package/dist/shared/identity.d.ts +1 -1
- package/dist/shared/layers.d.ts +7 -0
- package/dist/shared/mid-form.d.ts +53 -0
- package/dist/shared/reporting-gate.d.ts +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/visual-viewport-gap.d.ts +1 -1
- package/dist/signal/index.js +2 -2
- package/dist/signal/types.d.ts +1 -1
- package/dist/sitemap/index.js +4 -4
- package/dist/{socket-loader-R24ZSRSQ.js → socket-loader-CGIPEG74.js} +1 -1
- package/dist/sync/index.js +5 -5
- package/dist/types.d.ts +3 -1
- package/dist/{web-vitals.attribution-GD6LGLVF.js → web-vitals.attribution-N2PCCF4Q.js} +1 -1
- package/dist/website/BlocksPopup.d.ts +1 -1
- package/dist/website/PopupBlocks.d.ts +5 -2
- package/dist/website/SitePopups.d.ts +25 -0
- package/dist/website/images.js +4 -4
- package/dist/website/index.js +6 -6
- package/dist/website/popup-rules.d.ts +46 -0
- package/dist/website/popup-types.d.ts +113 -0
- package/dist/website/popups.d.ts +2 -6
- package/dist/website/popups.js +6 -14
- package/dist/{writeLLMsTxt-G3JBNBC5.js → writeLLMsTxt-2NQVDGYI.js} +3 -3
- package/docs/MIGRATING-TO-7.md +41 -9
- package/docs.json +2 -1
- package/package.json +2 -2
- package/src/analytics/README.md +2 -2
- package/src/articles/README.md +4 -1
- package/src/{engage → chat}/README.md +63 -62
- package/src/forms/README.md +49 -3
- package/src/layout/README.md +8 -5
- package/src/llms/README.md +69 -0
- package/src/mcp/README.md +50 -16
- package/src/seo/README.md +3 -1
- package/src/sync/README.md +10 -0
- package/src/website/README.md +133 -0
- package/dist/ChatWidget-UCDWMWCW.js +0 -15
- package/dist/EngageWidget-7ZYSK4GE.js +0 -11
- package/dist/ManagedForm-H7BVAH2W.js +0 -14
- package/dist/SitemapSync-XBANGKHZ.js +0 -8
- package/dist/chunk-NDF4A5JM.js +0 -37
- package/dist/chunk-VLARKWZU.js +0 -100
- package/dist/chunk-YCJT4JJG.js +0 -837
- package/dist/engage/DesignRenderer.d.ts +0 -57
- package/dist/engage/element-rules.d.ts +0 -38
package/src/forms/README.md
CHANGED
|
@@ -78,12 +78,12 @@ import { useForm } from '@sonordev/site-kit/forms'
|
|
|
78
78
|
|
|
79
79
|
export function ContactForm() {
|
|
80
80
|
const {
|
|
81
|
-
fields, values, errors, setFieldValue,
|
|
81
|
+
fields, values, errors, setFieldValue, handleSubmit, toolAttributes, isSubmitting,
|
|
82
82
|
step, totalSteps, isMultiStep, nextStep, prevStep, isLastStep,
|
|
83
83
|
} = useForm('contact-form')
|
|
84
84
|
|
|
85
85
|
return (
|
|
86
|
-
<form onSubmit={
|
|
86
|
+
<form {...toolAttributes} onSubmit={handleSubmit}>
|
|
87
87
|
{fields.map(field => (
|
|
88
88
|
<div key={field.slug}>
|
|
89
89
|
<label>{field.label}</label>
|
|
@@ -166,7 +166,12 @@ interface UseFormReturn {
|
|
|
166
166
|
canGoPrev: boolean
|
|
167
167
|
isLastStep: boolean
|
|
168
168
|
validate: () => boolean
|
|
169
|
-
|
|
169
|
+
// Resolves with what happened: { status: 'sent' | 'invalid' | 'failed' | 'busy' | 'next_step', ... }
|
|
170
|
+
submit: (options?: FormSubmitOptions) => Promise<FormSubmitOutcome>
|
|
171
|
+
// A ready-made <form onSubmit>: steps or submits, and answers an AI agent (see below)
|
|
172
|
+
handleSubmit: (event: FormEvent<HTMLFormElement>) => void
|
|
173
|
+
// WebMCP attributes for your <form>; null until the config loads
|
|
174
|
+
toolAttributes: { toolname: string; tooldescription: string } | null
|
|
170
175
|
isSubmitting: boolean
|
|
171
176
|
isComplete: boolean
|
|
172
177
|
reset: () => void
|
|
@@ -212,6 +217,47 @@ it, so Sonor refuses those before anything is written: the visitor sees an
|
|
|
212
217
|
error and you get no lead. Submit through site-kit, and define the form's
|
|
213
218
|
fields in Sonor so both can render and validate them.
|
|
214
219
|
|
|
220
|
+
## Agent-ready forms (7.2.0)
|
|
221
|
+
|
|
222
|
+
People increasingly ask an AI assistant to fill out a form for them: a quote
|
|
223
|
+
request, a booking, a newsletter signup. Assistants act on what the page's
|
|
224
|
+
markup says each control is, so managed forms now say it clearly. There's
|
|
225
|
+
nothing to configure.
|
|
226
|
+
|
|
227
|
+
- **Autocomplete tokens.** Name, email, phone, company, website and address
|
|
228
|
+
fields carry the right `autocomplete` token (`given-name`, `email`, `tel`,
|
|
229
|
+
`organization`, `url`, `postal-code`, ...), read from the field's CRM
|
|
230
|
+
destination in Sonor, then its type, slug and label. Browser autofill and
|
|
231
|
+
password managers use the same tokens.
|
|
232
|
+
- **Accessible wiring.** Error and help text are tied to their control
|
|
233
|
+
(`aria-describedby`), an errored control says so (`aria-invalid`), radio and
|
|
234
|
+
checkbox groups are named by their question, and rating stars by their
|
|
235
|
+
value.
|
|
236
|
+
- **WebMCP.** Every interactive managed form carries the declarative WebMCP
|
|
237
|
+
attributes (`toolname`, `tooldescription`), so a browser agent that supports
|
|
238
|
+
WebMCP can treat it as a tool. It fills the same fields a person would, and
|
|
239
|
+
the submit runs the same validation. There's no `toolautosubmit`: the
|
|
240
|
+
assistant fills the form and the person it's helping presses Send.
|
|
241
|
+
Browsers without WebMCP ignore the attributes.
|
|
242
|
+
- **Agent-sent leads are tagged.** When the browser reports that an agent
|
|
243
|
+
pressed submit (`SubmitEvent.agentInvoked`), the submission carries that
|
|
244
|
+
flag and the lead is tagged in Sonor, so you can see how those leads compare
|
|
245
|
+
over time, and Sonor's spam screening takes it into account. The agent is
|
|
246
|
+
handed the outcome of its submit (`respondWith`).
|
|
247
|
+
- **Nothing typed before the form loads is lost.** `ServerForm` renders the
|
|
248
|
+
form in the page HTML and loads the interactive version at idle. Since
|
|
249
|
+
7.2.0, typing into the server-rendered form starts that upgrade at once and
|
|
250
|
+
what was typed carries over, and a Send pressed before the upgrade is held
|
|
251
|
+
and sent the moment it lands. The server-rendered Send button stays disabled
|
|
252
|
+
until the page can catch the click.
|
|
253
|
+
|
|
254
|
+
With `useForm`, spread `toolAttributes` on your `<form>` and use
|
|
255
|
+
`handleSubmit` as its `onSubmit` to get the same behavior.
|
|
256
|
+
|
|
257
|
+
`ServerForm`'s `enhance` prop is ignored since 7.2.0. Only the interactive
|
|
258
|
+
form can send a managed form, so `enhance={false}` only ever produced a form
|
|
259
|
+
that silently went nowhere.
|
|
260
|
+
|
|
215
261
|
## Styles
|
|
216
262
|
|
|
217
263
|
```tsx
|
package/src/layout/README.md
CHANGED
|
@@ -28,9 +28,11 @@ interface SiteKitLayoutProps {
|
|
|
28
28
|
children: React.ReactNode
|
|
29
29
|
apiKey?: string // Defaults to SONOR_API_KEY env var
|
|
30
30
|
apiUrl?: string // Defaults to SONOR_API_URL, then https://api.sonor.io
|
|
31
|
-
projectId?: string // For
|
|
31
|
+
projectId?: string // For chat routing (auto-resolved if omitted)
|
|
32
32
|
analytics?: boolean | AnalyticsConfig // Default: true
|
|
33
|
-
|
|
33
|
+
chat?: boolean | ChatLayoutConfig // Default: true (website chat; config = launcher placement)
|
|
34
|
+
popups?: boolean // Default: true (Website → Popups & Banners)
|
|
35
|
+
engage?: boolean | EngageConfig // Deprecated: false turns chat and popups off; an object configures the launcher
|
|
34
36
|
signal?: boolean | SignalConfig // Default: false
|
|
35
37
|
sitemapSync?: boolean // Default: false (build-time + server reconciler own this)
|
|
36
38
|
fleet?: boolean // Default: true (once-per-session kit version heartbeat)
|
|
@@ -43,7 +45,7 @@ interface SiteKitLayoutProps {
|
|
|
43
45
|
}
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
Module options live with each module: [Analytics](../analytics/README.md) (`trackPageViews`, `excludePaths`, `site`, `allowInFrame`, `allowLocalhost`)
|
|
48
|
+
Module options live with each module: [Analytics](../analytics/README.md) (`trackPageViews`, `excludePaths`, `site`, `allowInFrame`, `allowLocalhost`) [Website chat](../chat/README.md) (`position`, `offsetBottom`, `zIndex`, `allowInFrame`) and [Popups and banners](../website/README.md).
|
|
47
49
|
|
|
48
50
|
## What It Composes
|
|
49
51
|
|
|
@@ -54,12 +56,13 @@ Module options live with each module: [Analytics](../analytics/README.md) (`trac
|
|
|
54
56
|
|
|
55
57
|
**Client-side (lazy-loaded island):**
|
|
56
58
|
- `AnalyticsProvider` — page views, scroll depth, heatmap clicks, Web Vitals
|
|
57
|
-
- `
|
|
59
|
+
- `SitePopups` — popups, banners and toasts
|
|
60
|
+
- `SiteChat` — website chat (Echo)
|
|
58
61
|
- `SignalBridge` — A/B experiments, behavior tracking (opt-in)
|
|
59
62
|
- `SitemapSync` — parses `/sitemap.xml` and syncs to Sonor (opt-in)
|
|
60
63
|
- `FleetHeartbeat` — reports the kit version and enabled modules once per session
|
|
61
64
|
|
|
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,
|
|
65
|
+
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, chat, popups, 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
66
|
|
|
64
67
|
## Note
|
|
65
68
|
|
package/src/llms/README.md
CHANGED
|
@@ -229,6 +229,75 @@ with `optimizedLLMsTxt: false` in `createSitemap`, so one writer owns the file.
|
|
|
229
229
|
Reads behind this write always bypass Next's Data Cache — hosts persist it
|
|
230
230
|
between builds, and a cached read is how a site shipped a months-old llms.txt.
|
|
231
231
|
|
|
232
|
+
#### Agent discovery for a custom MCP server
|
|
233
|
+
|
|
234
|
+
A custom server stays opt-in. Share the same `mcp` options between the
|
|
235
|
+
build-time writer and both route handlers so the short and full files point
|
|
236
|
+
at the same tools. This adds links to an existing server; it doesn't create
|
|
237
|
+
the endpoint, card, or catalog.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
// lib/agent-access.ts
|
|
241
|
+
import type { AgentAccessOptions } from '@sonordev/site-kit/seo/llms'
|
|
242
|
+
|
|
243
|
+
export const agentAccess: AgentAccessOptions = {
|
|
244
|
+
endpoint: '/api/mcp',
|
|
245
|
+
card: '/api/mcp/server-card',
|
|
246
|
+
catalog: '/.well-known/ai-catalog.json', // omit unless this document is served
|
|
247
|
+
position: 'after-intro',
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
// The site's build-time writer
|
|
253
|
+
import { writeLLMsTxtToPublic } from '@sonordev/site-kit/seo/llms'
|
|
254
|
+
import { agentAccess } from '../lib/agent-access'
|
|
255
|
+
|
|
256
|
+
await writeLLMsTxtToPublic({
|
|
257
|
+
full: true,
|
|
258
|
+
site: 'example.com',
|
|
259
|
+
mcp: agentAccess,
|
|
260
|
+
})
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// app/llms.txt/route.ts
|
|
265
|
+
import { createLLMsTxtHandler } from '@sonordev/site-kit/seo/llms'
|
|
266
|
+
import { agentAccess } from '@/lib/agent-access'
|
|
267
|
+
|
|
268
|
+
export const GET = createLLMsTxtHandler({
|
|
269
|
+
baseUrl: 'https://example.com',
|
|
270
|
+
mcp: agentAccess,
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
// app/llms-full.txt/route.ts — use the same options
|
|
276
|
+
import { createLLMsFullTxtHandler } from '@sonordev/site-kit/seo/llms'
|
|
277
|
+
import { agentAccess } from '@/lib/agent-access'
|
|
278
|
+
|
|
279
|
+
export const GET = createLLMsFullTxtHandler({
|
|
280
|
+
baseUrl: 'https://example.com',
|
|
281
|
+
mcp: agentAccess,
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`after-intro` preserves the title and introduction and puts Agent access
|
|
286
|
+
before the first content section. `end` places it at the end. Existing
|
|
287
|
+
markdown that already contains Agent access or a recognized server-card link
|
|
288
|
+
is preserved, so update a site's existing discovery section directly when
|
|
289
|
+
its URLs change. The optional catalog is an experimental discovery document,
|
|
290
|
+
not a requirement for using the MCP endpoint.
|
|
291
|
+
|
|
292
|
+
The writer applies these options to both files, whether Sonor generated the
|
|
293
|
+
content or local data supplied it. It resolves the origin from
|
|
294
|
+
`NEXT_PUBLIC_SITE_URL`, falling back to the `site` host. The handlers apply
|
|
295
|
+
explicit options even when serving an older file through `preferStatic`;
|
|
296
|
+
they use `baseUrl` or the configured site URL and never read the incoming
|
|
297
|
+
request. A CDN serving `public/llms.txt` directly still needs the build-time
|
|
298
|
+
writer to update that file. `mcp: false` disables automatic insertion; it
|
|
299
|
+
doesn't remove a site's existing discovery text.
|
|
300
|
+
|
|
232
301
|
### Step 5: On-Demand Revalidation (optional)
|
|
233
302
|
|
|
234
303
|
When Sonor data changes (page summaries, FAQs, etc.), bust the ISR cache instantly instead of waiting for `s-maxage` to expire:
|
package/src/mcp/README.md
CHANGED
|
@@ -9,7 +9,7 @@ definitions:
|
|
|
9
9
|
| Surface | Who uses it | Entry point |
|
|
10
10
|
|---|---|---|
|
|
11
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
15
|
One definition feeding all three is the point. The alternative — a tool list for
|
|
@@ -163,22 +163,45 @@ export const { POST, GET, DELETE, OPTIONS } = createMcpHandler({
|
|
|
163
163
|
export const dynamic = 'force-dynamic'
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
### 3. Mount the card
|
|
166
|
+
### 3. Mount the card and optional experimental catalog
|
|
167
|
+
|
|
168
|
+
The [ext-server-card discovery draft](https://github.com/modelcontextprotocol/ext-server-card/blob/main/docs/discovery.md)
|
|
169
|
+
is experimental. It recommends the card beside the endpoint at
|
|
170
|
+
`<streamable-http-url>/server-card` and a domain catalog at
|
|
171
|
+
`/.well-known/ai-catalog.json`. With the default endpoint, use
|
|
172
|
+
`/api/mcp/server-card` as the site's canonical card URL:
|
|
167
173
|
|
|
168
174
|
```ts
|
|
169
|
-
// app
|
|
170
|
-
|
|
171
|
-
import { createMcpServerCardHandler } from '@sonordev/site-kit/mcp'
|
|
175
|
+
// app/api/mcp/server-card/route.ts
|
|
176
|
+
import { createMcpServerCardHandler, MCP_SERVER_CARD_MEDIA_TYPE } from '@sonordev/site-kit/mcp'
|
|
172
177
|
import { mcpServer } from '@/lib/mcp/server'
|
|
173
178
|
|
|
174
179
|
export const { GET, OPTIONS } = createMcpServerCardHandler({
|
|
175
180
|
info: mcpServer.info,
|
|
176
181
|
tools: mcpServer.tools,
|
|
177
182
|
baseUrl: 'https://example.com',
|
|
183
|
+
contentType: MCP_SERVER_CARD_MEDIA_TYPE, // application/mcp-server-card+json
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// app/.well-known/ai-catalog.json/route.ts
|
|
189
|
+
import { createMcpAiCatalogHandler } from '@sonordev/site-kit/mcp'
|
|
190
|
+
import { mcpServer } from '@/lib/mcp/server'
|
|
191
|
+
|
|
192
|
+
export const { GET, OPTIONS } = createMcpAiCatalogHandler({
|
|
193
|
+
info: mcpServer.info,
|
|
194
|
+
baseUrl: 'https://example.com',
|
|
195
|
+
serverCardPath: '/api/mcp/server-card',
|
|
178
196
|
})
|
|
179
|
-
export const revalidate = 3600
|
|
180
197
|
```
|
|
181
198
|
|
|
199
|
+
The catalog returns `application/ai-catalog+json`. Both handlers use explicit
|
|
200
|
+
configuration and stay statically renderable without reading request data.
|
|
201
|
+
Keep existing `/.well-known/mcp-server-card` and `/.well-known/mcp.json`
|
|
202
|
+
aliases for clients that already use them. The card handler's default remains
|
|
203
|
+
`application/json`; set the vendor content type explicitly on the new path.
|
|
204
|
+
|
|
182
205
|
### 4. Register in-page (optional, for browser agents)
|
|
183
206
|
|
|
184
207
|
```tsx
|
|
@@ -188,10 +211,17 @@ export const revalidate = 3600
|
|
|
188
211
|
```
|
|
189
212
|
|
|
190
213
|
Pass no tool list. The component checks for WebMCP support first and only then
|
|
191
|
-
fetches `tools/list` from the endpoint
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
214
|
+
fetches `tools/list` from the endpoint. In a supported browser it starts when
|
|
215
|
+
the component mounts, without waiting for window load, an idle period, or a
|
|
216
|
+
human interaction. A browser without WebMCP makes no discovery request and
|
|
217
|
+
receives no catalog. Handing it descriptors from a server component instead
|
|
218
|
+
would put the whole catalog in every page's RSC payload.
|
|
219
|
+
|
|
220
|
+
`defer={true}` preserves the optional load + idle activation. Ordinary parent
|
|
221
|
+
renders keep the current registration; unmounting aborts discovery, remote
|
|
222
|
+
calls, and registrations that support the registration signal. If passing
|
|
223
|
+
`tools` or `localTools` explicitly, keep their arrays stable until the tools
|
|
224
|
+
change.
|
|
195
225
|
|
|
196
226
|
### 5. Rate-limit the public endpoint (Netlify)
|
|
197
227
|
|
|
@@ -268,8 +298,8 @@ is not re-exported from `@sonordev/site-kit/mcp`, which stays runtime-neutral.
|
|
|
268
298
|
|
|
269
299
|
### The card does not list tools — on purpose
|
|
270
300
|
|
|
271
|
-
|
|
272
|
-
vary with auth state and
|
|
301
|
+
The card's discovery shape omits primitives: what a server exposes can
|
|
302
|
+
vary with auth state and configuration, so the authoritative list is whatever
|
|
273
303
|
`tools/list` returns at call time. A card that inlined tools would be a second
|
|
274
304
|
source of truth that goes stale silently.
|
|
275
305
|
|
|
@@ -279,11 +309,15 @@ point and requires a reverse-DNS prefix, so the hint rides along without
|
|
|
279
309
|
pretending to be standard — useful for crawlers that index the card and never
|
|
280
310
|
connect.
|
|
281
311
|
|
|
282
|
-
###
|
|
312
|
+
### Experimental discovery and compatibility paths
|
|
283
313
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
314
|
+
Site-kit keeps `/.well-known/mcp-server-card` and `/.well-known/mcp.json`
|
|
315
|
+
for compatibility with existing integrations. The experimental
|
|
316
|
+
ext-server-card draft adds domain discovery through
|
|
317
|
+
`/.well-known/ai-catalog.json`, which links to the recommended endpoint-local
|
|
318
|
+
card at `/api/mcp/server-card`. These draft conventions aren't a guarantee
|
|
319
|
+
that every agent discovers or uses them. Keep the endpoint and existing card
|
|
320
|
+
URLs working while adding the catalog.
|
|
287
321
|
|
|
288
322
|
### Dual-era protocol support
|
|
289
323
|
|
package/src/seo/README.md
CHANGED
|
@@ -143,6 +143,8 @@ It renders one `application/ld+json` script (an `@graph` when there's more than
|
|
|
143
143
|
- a `BreadcrumbList` built from the path, when there isn't one already and the project has a site URL (skipped on `/`)
|
|
144
144
|
- a speakable `WebPage` or `Article` node, when `speakable`, `pageName` and `pageUrl` are all set
|
|
145
145
|
|
|
146
|
+
Everything Sonor supplies (its schema rows, `managed_schema` and the entity graph) loses any template placeholder first. A node whose values are a template's unfilled slots is dropped: a URL on a reserved example domain (`example.com`), a name like "Example" or "Your Business Name", a placeholder phone such as `+1-000-000-0000`, a slot like `[Resident Name]` or `{plan.name}`, or an object that's only a note about what goes there. Its real siblings and parents stay, so an `FAQPage` keeps its questions when only its publisher was a placeholder, and the `BreadcrumbList` fallback still applies when a placeholder breadcrumb is dropped. Your `additionalSchemas` are never touched. The rule is `@sonordev/contracts/schema-placeholders`.
|
|
147
|
+
|
|
146
148
|
| Prop | Default | Notes |
|
|
147
149
|
|------|---------|-------|
|
|
148
150
|
| `path` | | Required. |
|
|
@@ -157,7 +159,7 @@ It's wrapped in `Suspense`, so the fetch never holds up the rest of the page. Th
|
|
|
157
159
|
|
|
158
160
|
### `<LLMSchema path>`
|
|
159
161
|
|
|
160
|
-
Renders the page's `managed_llm_schema` as a `WebPage` JSON-LD script marked `data-llm-optimized="true"`, linked to the site's `WebSite` node when the project has a site URL. It renders nothing when the page has no LLM schema.
|
|
162
|
+
Renders the page's `managed_llm_schema` as a `WebPage` JSON-LD script marked `data-llm-optimized="true"`, linked to the site's `WebSite` node when the project has a site URL. It renders nothing when the page has no LLM schema, or when that schema is a template placeholder (see `<ManagedSchema>`).
|
|
161
163
|
|
|
162
164
|
### Schema helpers
|
|
163
165
|
|
package/src/sync/README.md
CHANGED
|
@@ -59,6 +59,16 @@ Expired meeting details and time reservations show a recovery action. Guests can
|
|
|
59
59
|
|
|
60
60
|
Navigation is disabled while a reservation or booking request is running. Stale responses cannot reopen a previous step, and holds created after the widget is removed are released.
|
|
61
61
|
|
|
62
|
+
## Accessibility
|
|
63
|
+
|
|
64
|
+
Screen readers and AI agent browsers (which act on elements by their accessible names) can book without guessing:
|
|
65
|
+
|
|
66
|
+
- **Days** are buttons named with the full date, year included: "Wednesday, October 7, 2026". The picked day is pressed (`aria-pressed="true"`), and days that can't be booked are disabled.
|
|
67
|
+
- **Times** are buttons named with the time and the date, "10:30 AM, Wednesday, October 7, 2026", in the booking's time zone. They still show just the time. The picked time is pressed.
|
|
68
|
+
- **Confirm**, the button that reserves the picked time, is named "Confirm 10:30 AM, Wednesday, October 7, 2026".
|
|
69
|
+
|
|
70
|
+
Every name contains what its button shows (a time's and Confirm's start with it), so voice control can still target a button by its visible text (WCAG 2.5.3). None of this changes what the widget sends.
|
|
71
|
+
|
|
62
72
|
## API Functions
|
|
63
73
|
|
|
64
74
|
```ts
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Popups and banners — `@sonordev/site-kit/website/popups`
|
|
2
|
+
|
|
3
|
+
The popups, banners and toasts you publish in Sonor (Website → Popups &
|
|
4
|
+
Banners), shown on your site where and when you set them, in the site's own
|
|
5
|
+
design.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
`SiteKitLayout` mounts it for you, after the page goes idle:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<SiteKitLayout>{children}</SiteKitLayout> // popups on (default)
|
|
13
|
+
<SiteKitLayout popups={false}>{children}</SiteKitLayout> // no popups
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
On a site without `SiteKitLayout`, mount `SitePopups` the same way:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
'use client'
|
|
20
|
+
import { SitePopups } from '@sonordev/site-kit/website/popups'
|
|
21
|
+
|
|
22
|
+
export function Popups() {
|
|
23
|
+
return <SitePopups />
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A project with nothing published costs one request and draws nothing. The
|
|
28
|
+
renderer downloads only when a popup is about to open.
|
|
29
|
+
|
|
30
|
+
## Props
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
interface SitePopupsProps {
|
|
34
|
+
apiUrl?: string // Default: from SiteKitLayout, else https://api.sonor.io
|
|
35
|
+
apiKey?: string // Default: from SiteKitLayout
|
|
36
|
+
zIndex?: number // The layer popups, banners and toasts sit on. Default: 9999
|
|
37
|
+
allowInFrame?: boolean // Show inside a cross-origin frame. Default: false
|
|
38
|
+
allowLocalhost?: boolean // Show on localhost. Default: false
|
|
39
|
+
debug?: boolean
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Through `SiteKitLayout`, `zIndex` comes from `chat={{ zIndex }}`, so the chat
|
|
44
|
+
launcher and popups share one layer.
|
|
45
|
+
|
|
46
|
+
## What shows
|
|
47
|
+
|
|
48
|
+
Each popup is a set of blocks (heading, formatted text, image, button,
|
|
49
|
+
divider) drawn in your site's design: its colors, fonts, radius and buttons.
|
|
50
|
+
`SiteKitLayout` measures the design from the home page at idle and reports it
|
|
51
|
+
to Sonor, so the popup builder previews popups the way they'll look. Its
|
|
52
|
+
`design` prop declares any part of it yourself
|
|
53
|
+
(`design={{ primary: 'var(--brand)', fontHeading: 'Fraunces, serif' }}`).
|
|
54
|
+
|
|
55
|
+
| Placement | What it is |
|
|
56
|
+
|-----------|------------|
|
|
57
|
+
| Popup | Centred over a dimmed page. One at a time: a second waits until the first closes |
|
|
58
|
+
| Banner | A bar along the top or bottom edge |
|
|
59
|
+
| Toast | A small card in a corner (slide-in) |
|
|
60
|
+
|
|
61
|
+
## Targeting and triggers
|
|
62
|
+
|
|
63
|
+
All set in Sonor, no code changes:
|
|
64
|
+
|
|
65
|
+
- **Pages**: include and exclude paths, with `*` for a prefix (`/services/*`)
|
|
66
|
+
- **Devices**: desktop, mobile, tablet
|
|
67
|
+
- **Schedule**: start and end dates
|
|
68
|
+
- **Trigger**: immediately, after a delay, at a scroll depth, or on exit intent
|
|
69
|
+
- **Frequency**: once, once per session, or every N days. Closing a popup and
|
|
70
|
+
pressing its button both count as seen.
|
|
71
|
+
|
|
72
|
+
A popup targeted at one page leaves when the visitor navigates away from it.
|
|
73
|
+
|
|
74
|
+
### Popups wait for forms
|
|
75
|
+
|
|
76
|
+
A popup or toast that opens on its own first checks whether the visitor is in
|
|
77
|
+
the middle of a form, and waits while they are. Mid-form means:
|
|
78
|
+
|
|
79
|
+
- focus is in a form field, or in a card field embedded in a form;
|
|
80
|
+
- a form has a field they've filled in and haven't sent yet;
|
|
81
|
+
- a site-kit form is sending.
|
|
82
|
+
|
|
83
|
+
It opens a moment after focus leaves the form, or once the form has been sent
|
|
84
|
+
and cleared. It never gives up: while the visitor stays mid-form, it keeps
|
|
85
|
+
waiting. Banners show at once, since they don't take focus or hide the page.
|
|
86
|
+
|
|
87
|
+
Why it matters: a modal popup takes focus and hides the rest of the page from
|
|
88
|
+
assistive technology while it's open. Opening one mid-form throws a person out
|
|
89
|
+
of the field they're typing in, and an AI agent browser filling the form for
|
|
90
|
+
someone (agents act on the accessibility tree) loses every field it was about
|
|
91
|
+
to fill.
|
|
92
|
+
|
|
93
|
+
## Accessibility
|
|
94
|
+
|
|
95
|
+
Popups, toasts and banners are built so people and agent browsers can find
|
|
96
|
+
them and their controls by name:
|
|
97
|
+
|
|
98
|
+
- **A popup** is a modal dialog (`role="dialog"`, `aria-modal="true"`) named
|
|
99
|
+
by its heading. It takes focus when it opens, keeps Tab inside, closes on
|
|
100
|
+
Escape or a click outside it, and puts focus back where it was.
|
|
101
|
+
- **A toast** is a named, non-modal dialog, and **a banner** is a named
|
|
102
|
+
region. Both close on Escape and leave focus where it is.
|
|
103
|
+
- **Close buttons are named "Close"**, whatever they show.
|
|
104
|
+
|
|
105
|
+
## Tracking
|
|
106
|
+
|
|
107
|
+
Each popup's impression (once half of it is on screen) and button presses are
|
|
108
|
+
counted in Sonor, against the same visitor ID (`_sk_vid`) analytics uses.
|
|
109
|
+
|
|
110
|
+
## Drawing one popup yourself
|
|
111
|
+
|
|
112
|
+
`PopupBlocks` is the renderer `SitePopups` uses, for drawing one popup from
|
|
113
|
+
its blocks (a preview, a custom placement):
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { PopupBlocks } from '@sonordev/site-kit/website/popups'
|
|
117
|
+
|
|
118
|
+
<PopupBlocks
|
|
119
|
+
blocks={[{ type: 'heading', text: 'Spring cleanups are booking' }]}
|
|
120
|
+
placement="popup"
|
|
121
|
+
onClose={() => setOpen(false)}
|
|
122
|
+
/>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`inline` renders it in place, without the overlay or focus handling.
|
|
126
|
+
|
|
127
|
+
## Engage (retired)
|
|
128
|
+
|
|
129
|
+
Engage was retired in Sonor, and its popups with it. Popups made in Engage
|
|
130
|
+
Studio (a `design_json` without blocks) aren't drawn by site-kit 7.2 or later;
|
|
131
|
+
make them again in Website → Popups & Banners. `@sonordev/site-kit/engage`
|
|
132
|
+
still builds through 7.x: its `EngageWidget` draws `SitePopups` and the
|
|
133
|
+
website chat.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export { ChatWidget, EchoChat, isChatEnabled } from './chunk-RHNWA34F.js';
|
|
2
|
-
import './chunk-JE4A5S4F.js';
|
|
3
|
-
import './chunk-NDF4A5JM.js';
|
|
4
|
-
import './chunk-6T6CQVNL.js';
|
|
5
|
-
import './chunk-V6FDQLR7.js';
|
|
6
|
-
import './chunk-AQSNPZY4.js';
|
|
7
|
-
import './chunk-5SQK3D53.js';
|
|
8
|
-
import './chunk-NJCTQH2P.js';
|
|
9
|
-
import './chunk-S22FSH7C.js';
|
|
10
|
-
import './chunk-L2V5PUFN.js';
|
|
11
|
-
import './chunk-GJWI74ZZ.js';
|
|
12
|
-
import './chunk-3SDOQUUF.js';
|
|
13
|
-
import './chunk-VDF5DFWT.js';
|
|
14
|
-
import './chunk-AH5Q262S.js';
|
|
15
|
-
import './chunk-PKBMQBKP.js';
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export { EngageWidget } from './chunk-YCJT4JJG.js';
|
|
2
|
-
import './chunk-NDF4A5JM.js';
|
|
3
|
-
import './chunk-6T6CQVNL.js';
|
|
4
|
-
import './chunk-24QZEO3Q.js';
|
|
5
|
-
import './chunk-GJWI74ZZ.js';
|
|
6
|
-
import './chunk-43OCZ3JA.js';
|
|
7
|
-
import './chunk-EKBEOXTH.js';
|
|
8
|
-
import './chunk-3SDOQUUF.js';
|
|
9
|
-
import './chunk-VDF5DFWT.js';
|
|
10
|
-
import './chunk-AH5Q262S.js';
|
|
11
|
-
import './chunk-PKBMQBKP.js';
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export { ManagedForm } from './chunk-CSTQDNGK.js';
|
|
2
|
-
import './chunk-D5EC5KVO.js';
|
|
3
|
-
import './chunk-DXO6SBMD.js';
|
|
4
|
-
import './chunk-LUXVWITO.js';
|
|
5
|
-
import './chunk-V6FDQLR7.js';
|
|
6
|
-
import './chunk-AQSNPZY4.js';
|
|
7
|
-
import './chunk-5SQK3D53.js';
|
|
8
|
-
import './chunk-NJCTQH2P.js';
|
|
9
|
-
import './chunk-L2V5PUFN.js';
|
|
10
|
-
import './chunk-GJWI74ZZ.js';
|
|
11
|
-
import './chunk-3SDOQUUF.js';
|
|
12
|
-
import './chunk-VDF5DFWT.js';
|
|
13
|
-
import './chunk-AH5Q262S.js';
|
|
14
|
-
import './chunk-PKBMQBKP.js';
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { SitemapSync } from './chunk-UXOGCKL6.js';
|
|
2
|
-
import './chunk-YVKRYQRG.js';
|
|
3
|
-
import './chunk-43OCZ3JA.js';
|
|
4
|
-
import './chunk-EKBEOXTH.js';
|
|
5
|
-
import './chunk-3SDOQUUF.js';
|
|
6
|
-
import './chunk-VDF5DFWT.js';
|
|
7
|
-
import './chunk-AH5Q262S.js';
|
|
8
|
-
import './chunk-PKBMQBKP.js';
|
package/dist/chunk-NDF4A5JM.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { VISUAL_VIEWPORT_GAP_PROPERTY } from './chunk-6T6CQVNL.js';
|
|
2
|
-
|
|
3
|
-
// src/engage/launcher-placement.ts
|
|
4
|
-
var DEFAULT_LAUNCHER_OFFSET_BOTTOM = "20px";
|
|
5
|
-
var DEFAULT_ENGAGE_Z_INDEX = 9999;
|
|
6
|
-
var LAUNCHER_OFFSET_PROPERTY = "--sk-echo-offset-bottom";
|
|
7
|
-
var SIDE_GUTTER = "20px";
|
|
8
|
-
var POPUP_ABOVE_LAUNCHER = "70px";
|
|
9
|
-
var POPUP_VERTICAL_RESERVE = "100px";
|
|
10
|
-
function toCssLength(value) {
|
|
11
|
-
if (typeof value === "number") return Number.isFinite(value) ? `${value}px` : void 0;
|
|
12
|
-
const trimmed = value?.trim();
|
|
13
|
-
return trimmed ? trimmed : void 0;
|
|
14
|
-
}
|
|
15
|
-
function chatLauncherStyles({ position, offsetBottom, zIndex } = {}) {
|
|
16
|
-
const side = position === "bottom-left" ? { left: SIDE_GUTTER } : { right: SIDE_GUTTER };
|
|
17
|
-
const offset = toCssLength(offsetBottom) ?? DEFAULT_LAUNCHER_OFFSET_BOTTOM;
|
|
18
|
-
const layer = typeof zIndex === "number" && Number.isFinite(zIndex) ? zIndex : DEFAULT_ENGAGE_Z_INDEX;
|
|
19
|
-
const clearance = `var(${LAUNCHER_OFFSET_PROPERTY}, ${offset}) + env(safe-area-inset-bottom, 0px) + var(${VISUAL_VIEWPORT_GAP_PROPERTY}, 0px)`;
|
|
20
|
-
return {
|
|
21
|
-
launcher: {
|
|
22
|
-
...side,
|
|
23
|
-
bottom: `calc(${clearance})`
|
|
24
|
-
},
|
|
25
|
-
popup: {
|
|
26
|
-
...side,
|
|
27
|
-
bottom: `calc(${clearance} + ${POPUP_ABOVE_LAUNCHER})`,
|
|
28
|
-
maxHeight: `calc(100dvh - (${clearance}) - ${POPUP_VERTICAL_RESERVE})`
|
|
29
|
-
},
|
|
30
|
-
// One beneath the launcher. At 0 or below, the same layer instead: -1
|
|
31
|
-
// would paint the popup behind the page's own content, and at a shared
|
|
32
|
-
// layer the launcher, which renders after the popup, still paints on top.
|
|
33
|
-
zIndex: { launcher: layer, popup: layer > 0 ? layer - 1 : layer }
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export { DEFAULT_ENGAGE_Z_INDEX, DEFAULT_LAUNCHER_OFFSET_BOTTOM, chatLauncherStyles };
|