@sonordev/site-kit 7.1.1 → 7.2.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 +3 -3
- package/CHANGELOG.md +68 -3426
- package/README.md +9 -8
- package/agent-manifest.json +12 -4
- package/dist/{AnalyticsProvider-3MI5RCTL.js → AnalyticsProvider-SLFFBRPE.js} +4 -4
- package/dist/{ArticleViewTracker-CMBSJR3N.js → ArticleViewTracker-TM5AMGN2.js} +3 -3
- package/dist/{BlocksPopup-X3OAAK2L.js → BlocksPopup-YJNJIVPO.js} +5 -5
- package/dist/ChatWidget-XKOSH66S.js +17 -0
- package/dist/{FileField-QRSEGO6D.js → FileField-CI4UL5SH.js} +3 -3
- package/dist/{FormSpotlight-XKV7VTN4.js → FormSpotlight-F2AAEGBN.js} +20 -6
- package/dist/{FormStage-C3YGYAUZ.js → FormStage-BI5R5CNC.js} +27 -7
- package/dist/ManagedForm-G6COGHXL.js +16 -0
- package/dist/{ManagedNewsletterForm-LPIKGJF4.js → ManagedNewsletterForm-BO3WCGXX.js} +7 -5
- package/dist/{SignalCore-ICXXZBYI.js → SignalCore-ZA6WHKBR.js} +3 -3
- package/dist/SiteChat-YJADDTKG.js +5 -0
- package/dist/{SiteDesignReporter-A7MS2CIP.js → SiteDesignReporter-5YAW36AQ.js} +5 -5
- package/dist/SitePopups-ST5PMKZE.js +10 -0
- package/dist/SitemapSync-RJTEBINX.js +8 -0
- package/dist/SitemapSync.d.ts +1 -1
- package/dist/_client/booking-widget.js +5 -5
- package/dist/_client/testimonial-section.d.ts +1 -1
- package/dist/affiliates/api.d.ts +1 -1
- package/dist/affiliates/index.js +3 -3
- package/dist/analytics/AnalyticsProvider.d.ts +1 -1
- package/dist/analytics/index.js +4 -4
- package/dist/analytics/send-gate.d.ts +13 -26
- package/dist/articles/Article.d.ts +1 -1
- package/dist/articles/ArticleList.d.ts +1 -1
- package/dist/articles/ClusterLandingPage.d.ts +2 -2
- package/dist/articles/PublicationLayout.d.ts +1 -1
- package/dist/articles/PublicationSidebar.d.ts +1 -1
- package/dist/articles/RelatedPosts.d.ts +1 -1
- package/dist/articles/author-schema.d.ts +1 -1
- package/dist/articles/excerpt.d.ts +3 -4
- package/dist/articles/index.d.ts +2 -2
- package/dist/articles/index.js +2 -2
- package/dist/articles/news-sitemap.d.ts +1 -1
- package/dist/articles/processArticleHtml.d.ts +1 -1
- package/dist/articles/server-core.d.ts +4 -4
- package/dist/articles/server-ui.js +3 -2
- package/dist/articles/server.d.ts +4 -4
- package/dist/articles/server.js +2 -1
- package/dist/articles/types.d.ts +1 -1
- package/dist/{engage → chat}/ChatWidget.d.ts +8 -7
- package/dist/{engage → chat}/EchoUiActions.d.ts +4 -4
- package/dist/chat/SiteChat.d.ts +25 -0
- package/dist/{engage → chat}/brand-color.d.ts +2 -2
- 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-QPEEKFCA.js → chunk-4NTBQNHA.js} +249 -80
- package/dist/{chunk-RK7GF7PQ.js → chunk-5V2V5C3Y.js} +2 -4
- package/dist/{chunk-ICFRH2ED.js → chunk-5YCEMV2L.js} +1 -1
- package/dist/{chunk-V4HUSHZO.js → chunk-64DKGVEI.js} +41 -7
- package/dist/{chunk-GLHQ3LRO.js → chunk-6U3VLV2C.js} +1 -1
- package/dist/{chunk-ZETJTCMV.js → chunk-BC4Y3S3D.js} +1 -1
- package/dist/{chunk-JSZN6LDZ.js → chunk-BF7TZYC3.js} +38 -31
- package/dist/{chunk-EFPS56JA.js → chunk-BKY2ZNM7.js} +1 -1
- package/dist/{chunk-AQSNPZY4.js → chunk-C2FZBUSS.js} +80 -1
- package/dist/{chunk-H42PZKVC.js → chunk-CF5QNFYU.js} +2 -2
- package/dist/{chunk-JC7KXNNM.js → chunk-D5DCBD4Y.js} +2 -2
- package/dist/{chunk-PXZ7LAOY.js → chunk-D6ZC7XKZ.js} +2 -2
- package/dist/chunk-EJVQJCG5.js +370 -0
- package/dist/{chunk-6OL23QYD.js → chunk-F24FNCTK.js} +1 -1
- package/dist/{chunk-NEKEH4CK.js → chunk-FR6BYFPC.js} +53 -21
- package/dist/{chunk-KOPVJQ2P.js → chunk-GEXT7BTE.js} +1 -1
- package/dist/{chunk-ZAWANWUS.js → chunk-GGORNBSC.js} +18 -6
- package/dist/{chunk-DUQMSNF2.js → chunk-HGFAEDG3.js} +1 -1
- package/dist/chunk-HODO5BX5.js +28 -0
- package/dist/chunk-HW7B43E5.js +14 -0
- package/dist/{chunk-EQBZ24VH.js → chunk-IAM3D3RL.js} +43 -24
- package/dist/{chunk-ODADQM6Z.js → chunk-JJ3DELUL.js} +2 -2
- package/dist/{chunk-542MIHNP.js → chunk-JMLK6MH7.js} +1 -1
- package/dist/{chunk-7FWA3I6A.js → chunk-JWTQS5F5.js} +1 -1
- package/dist/{chunk-OUNRFXDC.js → chunk-K4ZHAURD.js} +1 -1
- package/dist/chunk-NV72HZ4W.js +45 -0
- package/dist/{chunk-4Y5FWDPM.js → chunk-NZ4RXN3G.js} +55 -7
- package/dist/chunk-OWZ7ATLZ.js +187 -0
- package/dist/{chunk-TXBAOEKG.js → chunk-PKGN32AU.js} +5 -4
- package/dist/chunk-Q6E4OPGG.js +40 -0
- package/dist/{chunk-J4D6ZXRW.js → chunk-QL6XNPSD.js} +3 -3
- package/dist/{chunk-HAG4YIZY.js → chunk-R46IQIGK.js} +6 -6
- package/dist/{chunk-R3VKF43H.js → chunk-RPZ7OQGD.js} +1 -1
- package/dist/{chunk-Q3S26EQA.js → chunk-S54ECQGY.js} +1 -1
- package/dist/{chunk-JIL5VY2R.js → chunk-VHKEF5ZB.js} +1 -1
- package/dist/{chunk-ILAW3XKD.js → chunk-VO3JYRT5.js} +43 -29
- package/dist/chunk-VWWITFWM.js +300 -0
- package/dist/{chunk-OPRBF7VE.js → chunk-ZTLMPUO6.js} +3 -2
- package/dist/client/index.js +3 -3
- package/dist/cms/CmsPreview.d.ts +2 -2
- package/dist/cms/index.d.ts +3 -3
- package/dist/cms/server-api.d.ts +1 -1
- package/dist/cms/types.d.ts +1 -1
- package/dist/commerce/EventCheckout.d.ts +1 -1
- package/dist/commerce/EventsAgenda.d.ts +3 -3
- package/dist/commerce/api.d.ts +1 -1
- package/dist/commerce/index.js +4 -4
- package/dist/commerce/server.d.ts +3 -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/fleet.d.ts +9 -9
- package/dist/contracts/forms.d.ts +4 -10
- package/dist/contracts/llms.d.ts +2 -1
- package/dist/contracts/portfolio.d.ts +11 -15
- package/dist/contracts/proposal-sitemap.d.ts +130 -0
- package/dist/contracts/schema-placeholders.d.ts +87 -0
- package/dist/contracts/seo-meta.d.ts +11 -12
- package/dist/contracts/seo-pages.d.ts +12 -18
- package/dist/contracts/site-cache.d.ts +4 -4
- package/dist/contracts/sites-normalize.d.ts +2 -4
- package/dist/contracts/sites.d.ts +3 -5
- package/dist/contracts/slot-content.d.ts +2 -2
- package/dist/contracts/slots.d.ts +6 -6
- package/dist/contracts/voice.d.ts +56 -0
- package/dist/contracts/website.d.ts +2 -2
- 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 +2 -2
- package/dist/fleet/index.js +4 -4
- package/dist/forms/FormEnhancer.d.ts +25 -12
- package/dist/forms/FormSpotlight.d.ts +2 -2
- package/dist/forms/ManagedForm.d.ts +1 -1
- package/dist/forms/ServerForm.d.ts +11 -10
- package/dist/forms/StaticForm.d.ts +4 -2
- package/dist/forms/field-autocomplete.d.ts +28 -0
- package/dist/forms/field-interactions.d.ts +2 -2
- package/dist/forms/form-dom-values.d.ts +42 -0
- package/dist/forms/form-loaded-at.d.ts +2 -2
- package/dist/forms/formsApi.d.ts +2 -2
- package/dist/forms/index.d.ts +1 -1
- package/dist/forms/index.js +11 -9
- package/dist/forms/server.d.ts +1 -1
- package/dist/forms/server.js +6 -4
- package/dist/forms/static.js +3 -2
- package/dist/forms/submitForm.d.ts +9 -2
- package/dist/forms/types.d.ts +44 -5
- package/dist/forms/useForm.d.ts +21 -4
- package/dist/forms/webmcp.d.ts +38 -0
- package/dist/images/ManagedFavicon.d.ts +1 -1
- package/dist/images/ManagedImage.d.ts +4 -4
- package/dist/images/api.d.ts +1 -1
- package/dist/images/index.d.ts +2 -2
- package/dist/images/index.js +4 -4
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/dist/landing/contract.d.ts +3 -4
- package/dist/layout/SiteKitClientProviders.d.ts +4 -4
- package/dist/layout/SiteKitLayout.d.ts +11 -11
- package/dist/layout/client.d.ts +4 -4
- package/dist/layout/client.js +7 -7
- package/dist/layout/index.js +8 -8
- package/dist/layout/types.d.ts +7 -6
- package/dist/llms/SpeakableSchema.d.ts +2 -2
- package/dist/llms/agent-access.d.ts +2 -2
- package/dist/llms/aiRobots.d.ts +4 -4
- package/dist/llms/api.d.ts +2 -2
- package/dist/llms/contract.js +1 -1
- package/dist/llms/generateLLMsTxt.d.ts +1 -1
- package/dist/llms/index.js +6 -6
- package/dist/llms/links.d.ts +1 -1
- package/dist/llms/revalidate.d.ts +1 -1
- package/dist/llms/seo-revalidate.d.ts +2 -3
- package/dist/llms/types.d.ts +6 -6
- package/dist/llms/writeLLMsTxt.d.ts +10 -10
- package/dist/manifest/index.d.ts +8 -8
- package/dist/maps/index.js +3 -3
- package/dist/mcp/WebMcpTools.d.ts +1 -1
- package/dist/mcp/index.js +2 -14
- package/dist/mcp/report.d.ts +2 -2
- package/dist/mcp/serverCard.d.ts +1 -1
- package/dist/mcp/sonor.d.ts +5 -6
- package/dist/mcp/sonor.js +14 -11
- package/dist/mcp/transport.d.ts +1 -2
- package/dist/mcp/types.d.ts +4 -5
- package/dist/motion/engine.d.ts +2 -3
- package/dist/motion/failsafe.d.ts +1 -2
- package/dist/motion/gsap.d.ts +2 -2
- package/dist/motion/three.d.ts +1 -1
- package/dist/og/config.d.ts +4 -4
- package/dist/og/contrast.d.ts +2 -2
- package/dist/og/pages.d.ts +5 -5
- package/dist/og/route-fit.d.ts +2 -2
- package/dist/og/route.d.ts +1 -2
- package/dist/og/template.d.ts +1 -1
- package/dist/proxy/index.d.ts +2 -2
- package/dist/proxy/securityHeaders.d.ts +10 -12
- package/dist/redirects/index.d.ts +7 -7
- package/dist/reputation/TestimonialSection.d.ts +1 -1
- package/dist/reputation/api.d.ts +1 -1
- package/dist/revalidate/index.js +2 -2
- package/dist/robots/index.d.ts +5 -5
- package/dist/robots/indexnow.d.ts +2 -3
- package/dist/seo/ManagedScripts.d.ts +2 -2
- package/dist/seo/client.js +4 -4
- package/dist/seo/getManagedMetadata.d.ts +1 -1
- package/dist/seo/groundingSchema.d.ts +2 -2
- 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-impl.d.ts +1 -1
- package/dist/seo/register-sitemap-cli.d.ts +1 -1
- package/dist/seo/register-sitemap-cli.js +2 -2
- package/dist/seo/schema-filter.d.ts +5 -6
- package/dist/seo/server-api.d.ts +1 -1
- package/dist/seo/sitemap.js +4 -4
- package/dist/seo/types.d.ts +4 -4
- package/dist/seo/withManagedMetadata.d.ts +4 -4
- package/dist/server/index.js +2 -2
- package/dist/server/mint-site-token.d.ts +1 -2
- package/dist/server/server-fetch.d.ts +5 -5
- package/dist/shared/build-entries.d.ts +4 -4
- package/dist/shared/dialog.d.ts +27 -0
- package/dist/shared/frame.d.ts +8 -9
- package/dist/shared/fresh-fetch.d.ts +1 -2
- 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/next-files.d.ts +1 -1
- package/dist/shared/publishCredential.d.ts +1 -1
- package/dist/shared/reporting-gate.d.ts +1 -1
- package/dist/shared/uuid.d.ts +1 -2
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/visual-viewport-gap.d.ts +3 -3
- package/dist/signal/index.js +2 -2
- package/dist/signal/types.d.ts +1 -1
- package/dist/site-config/index.d.ts +2 -2
- package/dist/sitemap/index.d.ts +13 -13
- package/dist/sitemap/index.js +4 -4
- package/dist/sitemap/shared.d.ts +3 -3
- package/dist/sites/site-param.d.ts +2 -2
- package/dist/slots/ManagedRichText.d.ts +1 -1
- package/dist/slots/ManagedSlot.d.ts +1 -1
- package/dist/slots/index.d.ts +1 -3
- package/dist/{socket-loader-R24ZSRSQ.js → socket-loader-CGIPEG74.js} +1 -1
- package/dist/sync/index.js +5 -5
- package/dist/types.d.ts +4 -2
- package/dist/website/BlocksPopup.d.ts +1 -1
- package/dist/website/PopupBlocks.d.ts +8 -6
- 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-QR23OQUE.js → writeLLMsTxt-OV24LQVL.js} +3 -3
- package/docs/MIGRATING-TO-7.md +50 -22
- package/docs.json +2 -1
- package/package.json +2 -2
- package/src/admin-auth/README.md +3 -3
- package/src/analytics/README.md +9 -11
- package/src/articles/README.md +10 -7
- package/src/{engage → chat}/README.md +63 -62
- package/src/cta-bar/README.md +11 -11
- package/src/forms/README.md +60 -12
- package/src/layout/README.md +8 -5
- package/src/llms/README.md +3 -3
- package/src/mcp/README.md +7 -7
- package/src/motion/README.md +1 -1
- package/src/og/README.md +26 -27
- package/src/proxy/README.md +14 -14
- package/src/seo/README.md +4 -2
- package/src/slots/README.md +1 -1
- package/src/sync/README.md +10 -0
- package/src/website/README.md +133 -0
- package/dist/ChatWidget-JMFBXJ75.js +0 -15
- package/dist/EngageWidget-A3QKZ3SU.js +0 -11
- package/dist/ManagedForm-2GSSDJPP.js +0 -14
- package/dist/SitemapSync-4U654SEA.js +0 -8
- package/dist/chunk-B73QPZPH.js +0 -837
- package/dist/chunk-NDF4A5JM.js +0 -37
- package/dist/chunk-RTMMHTEQ.js +0 -100
- package/dist/engage/DesignRenderer.d.ts +0 -57
- package/dist/engage/element-rules.d.ts +0 -38
|
@@ -1,58 +1,69 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Website chat — `@sonordev/site-kit/chat`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Echo's chat launcher and conversation on your site, configured in Sonor
|
|
4
|
+
(Messages → Chat settings). AI answers, live handoff to your team, and an
|
|
5
|
+
offline form when nobody's around.
|
|
4
6
|
|
|
5
7
|
## Usage
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
`SiteKitLayout` mounts it for you, after the page goes idle:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<SiteKitLayout>{children}</SiteKitLayout> // chat on (default)
|
|
13
|
+
<SiteKitLayout chat={{ offsetBottom: '88px' }}>{children}</SiteKitLayout>
|
|
14
|
+
<SiteKitLayout chat={false}>{children}</SiteKitLayout> // no chat
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
On a site without `SiteKitLayout`, mount `SiteChat` the same way:
|
|
8
18
|
|
|
9
19
|
```tsx
|
|
10
20
|
'use client'
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
return
|
|
15
|
-
<>
|
|
16
|
-
{children}
|
|
17
|
-
<EngageWidget />
|
|
18
|
-
</>
|
|
19
|
-
)
|
|
21
|
+
import { SiteChat } from '@sonordev/site-kit/chat'
|
|
22
|
+
|
|
23
|
+
export function Chat() {
|
|
24
|
+
return <SiteChat position="bottom-right" />
|
|
20
25
|
}
|
|
21
26
|
```
|
|
22
27
|
|
|
28
|
+
`SiteChat` waits for the browser to go idle, skips cross-origin frames and
|
|
29
|
+
localhost (like analytics), and downloads the chat widget only when it
|
|
30
|
+
renders. `ChatWidget` is the widget itself, for a page that resolves those
|
|
31
|
+
choices on its own.
|
|
32
|
+
|
|
23
33
|
## Props
|
|
24
34
|
|
|
25
35
|
```ts
|
|
26
|
-
interface
|
|
27
|
-
apiUrl?: string
|
|
28
|
-
apiKey?: string
|
|
29
|
-
projectId?: string
|
|
36
|
+
interface SiteChatProps {
|
|
37
|
+
apiUrl?: string // Default: from SiteKitLayout, else https://api.sonor.io
|
|
38
|
+
apiKey?: string // Default: from SiteKitLayout
|
|
39
|
+
projectId?: string // For chat routing. Resolved from the key when absent
|
|
30
40
|
position?: 'bottom-right' | 'bottom-left' // Default: 'bottom-right'
|
|
31
41
|
offsetBottom?: string | number // Default: '20px'. See "Launcher placement"
|
|
32
|
-
zIndex?: number
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
zIndex?: number // Default: 9999. See "Stacking"
|
|
43
|
+
allowInFrame?: boolean // Show inside a cross-origin frame. Default: false
|
|
44
|
+
allowLocalhost?: boolean // Show on localhost. Default: false
|
|
35
45
|
}
|
|
36
46
|
```
|
|
37
47
|
|
|
38
|
-
Through `SiteKitLayout`, the
|
|
48
|
+
Through `SiteKitLayout`, the placement options go in `chat={{ ... }}`. The
|
|
49
|
+
deprecated `engage={{ ... }}` still works through 7.x.
|
|
39
50
|
|
|
40
51
|
## The chat switch
|
|
41
52
|
|
|
42
|
-
Echo follows the project's **
|
|
43
|
-
Chat settings). A project that has never saved chat settings
|
|
44
|
-
Echo is on by default and only an owner who switches it off
|
|
45
|
-
needs the project's **
|
|
46
|
-
answers off, since the chat has nowhere to take a
|
|
53
|
+
Echo follows the project's **Show the chat widget** switch in Sonor
|
|
54
|
+
(Messages → Chat settings). A project that has never saved chat settings
|
|
55
|
+
counts as on, so Echo is on by default and only an owner who switches it off
|
|
56
|
+
hides it. It also needs the project's **Website chat** module on (Project
|
|
57
|
+
Settings); without it Sonor answers off, since the chat has nowhere to take a
|
|
58
|
+
visitor's message.
|
|
47
59
|
|
|
48
60
|
- The launcher appears once `GET /engage/widget/config` answers. Nothing
|
|
49
61
|
renders before that, so a switched-off site never flashes a launcher, and
|
|
50
62
|
it never starts Echo's availability polling.
|
|
51
63
|
- If the config can't be fetched, the launcher stays hidden. The chat can't
|
|
52
64
|
run without the API anyway.
|
|
53
|
-
- `
|
|
54
|
-
|
|
55
|
-
the owner's switch.
|
|
65
|
+
- `chat={false}` in code still turns chat off whatever the switch says. Code
|
|
66
|
+
can turn Echo off; it can't force it on over the owner's switch.
|
|
56
67
|
|
|
57
68
|
## Liquid Glass (6.1.0)
|
|
58
69
|
|
|
@@ -75,7 +86,7 @@ recipe (`src/shared/glass.tsx`), the same material as the mobile CTA bar
|
|
|
75
86
|
panel, and otherwise pulls it toward black (light panel) or white (dark
|
|
76
87
|
panel) just far enough to clear it: `#d4af37` gold becomes `#887023`. Fills
|
|
77
88
|
(launcher, avatar, the visitor's bubbles, buttons) keep the raw brand. One
|
|
78
|
-
helper decides this, `src/
|
|
89
|
+
helper decides this, `src/chat/brand-color.ts`; route any new brand
|
|
79
90
|
foreground through it. `--sk-primary` can be hex, `rgb()` or `hsl()`.
|
|
80
91
|
- **Fields are 16px.** iOS Safari zooms the whole page into any focused field
|
|
81
92
|
under 16px; the chat input and the inline Echo forms were 13.5-14px.
|
|
@@ -104,7 +115,7 @@ it without `!important`. Don't write that override; declare the offset instead.
|
|
|
104
115
|
**A fixed offset, every page:** pass `offsetBottom`.
|
|
105
116
|
|
|
106
117
|
```tsx
|
|
107
|
-
<SiteKitLayout
|
|
118
|
+
<SiteKitLayout chat={{ offsetBottom: '88px' }}>{children}</SiteKitLayout>
|
|
108
119
|
```
|
|
109
120
|
|
|
110
121
|
Any CSS length works (`'5.5rem'`, `'calc(4rem + 8px)'`); a number is pixels.
|
|
@@ -148,13 +159,14 @@ popup maxHeight = 100dvh - launcher bottom - 100px
|
|
|
148
159
|
|
|
149
160
|
### Stacking
|
|
150
161
|
|
|
151
|
-
`zIndex` is the
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
when your own fixed UI (a mobile menu, a cookie
|
|
162
|
+
`zIndex` is the layer the chat launcher sits on, with the chat window one
|
|
163
|
+
layer beneath it. Through `SiteKitLayout` the same number also stacks the
|
|
164
|
+
site's popups, banners and toasts. The default is 9999, above almost anything
|
|
165
|
+
a site draws, so lower it when your own fixed UI (a mobile menu, a cookie
|
|
166
|
+
banner) has to cover the chat:
|
|
155
167
|
|
|
156
168
|
```tsx
|
|
157
|
-
<SiteKitLayout
|
|
169
|
+
<SiteKitLayout chat={{ zIndex: 40 }}>{children}</SiteKitLayout>
|
|
158
170
|
```
|
|
159
171
|
|
|
160
172
|
```
|
|
@@ -162,9 +174,9 @@ launcher z-index = zIndex (default 9999)
|
|
|
162
174
|
popup z-index = zIndex - 1
|
|
163
175
|
```
|
|
164
176
|
|
|
165
|
-
At `zIndex` 0 or below the
|
|
166
|
-
-1 would put it behind the page's own content. The launcher still
|
|
167
|
-
top.
|
|
177
|
+
At `zIndex` 0 or below the chat window shares the launcher's layer instead,
|
|
178
|
+
because -1 would put it behind the page's own content. The launcher still
|
|
179
|
+
paints on top.
|
|
168
180
|
|
|
169
181
|
### Layout vs visual viewport on phones
|
|
170
182
|
|
|
@@ -194,29 +206,9 @@ It's opt-in: while a field has focus the gap grows to the keyboard's height,
|
|
|
194
206
|
so everything that reads it rides above the keyboard. `useVisualViewportGap()`
|
|
195
207
|
is the hook form for an existing client component.
|
|
196
208
|
|
|
197
|
-
##
|
|
198
|
-
|
|
199
|
-
| Type | Description |
|
|
200
|
-
|------|-------------|
|
|
201
|
-
| `popup` | Modal overlay with CTA |
|
|
202
|
-
| `nudge` | Small corner notification |
|
|
203
|
-
| `bar` | Top/bottom sticky bar |
|
|
204
|
-
| `slide-in` | Side panel |
|
|
205
|
-
| `chat` | AI/live chat widget |
|
|
209
|
+
## Chat config
|
|
206
210
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
All configured in the Sonor dashboard — no code changes needed:
|
|
210
|
-
|
|
211
|
-
- **Page targeting** — include/exclude paths with wildcard support
|
|
212
|
-
- **Device targeting** — desktop, mobile, tablet
|
|
213
|
-
- **Visitor targeting** — new vs returning visitors
|
|
214
|
-
- **Triggers** — immediate, delay (seconds), scroll (%), exit-intent, click, custom
|
|
215
|
-
- **Frequency capping** — once, once-per-session, every N days
|
|
216
|
-
|
|
217
|
-
## Chat Widget
|
|
218
|
-
|
|
219
|
-
Supports AI mode (Echo), live mode, and hybrid (AI + human handoff):
|
|
211
|
+
AI mode (Echo), live mode, and hybrid (AI with a human handoff). `ChatWidget` takes these as `config`; Sonor sends the rest from Chat settings:
|
|
220
212
|
|
|
221
213
|
```ts
|
|
222
214
|
interface ChatConfig {
|
|
@@ -236,6 +228,15 @@ interface ChatConfig {
|
|
|
236
228
|
}
|
|
237
229
|
```
|
|
238
230
|
|
|
239
|
-
##
|
|
231
|
+
## Popups
|
|
232
|
+
|
|
233
|
+
Popups, banners and toasts are their own module now:
|
|
234
|
+
[Popups and banners](../website/README.md) (`@sonordev/site-kit/website/popups`).
|
|
235
|
+
|
|
236
|
+
## `@sonordev/site-kit/engage` (deprecated)
|
|
240
237
|
|
|
241
|
-
|
|
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.
|
package/src/cta-bar/README.md
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
The Liquid Glass mobile CTA bar (6.1.0). A floating frosted capsule that
|
|
4
4
|
keeps a site's one or two highest-intent actions a thumb away on phones.
|
|
5
5
|
|
|
6
|
-
It replaces
|
|
7
|
-
|
|
6
|
+
It replaces a hand-rolled sticky mobile bar, and handles what each of those
|
|
7
|
+
ends up solving on its own:
|
|
8
8
|
|
|
9
|
-
| Behaviour |
|
|
10
|
-
|
|
11
|
-
| Hide while the form it points at is on screen |
|
|
12
|
-
| Stay off the hero until the hero CTA scrolls away |
|
|
13
|
-
| Get out from under the Echo launcher |
|
|
14
|
-
| Ride out the iOS toolbar collapsing |
|
|
15
|
-
| Get out of the way of the keyboard |
|
|
16
|
-
| Tell Sonor which action converts |
|
|
9
|
+
| Behaviour | How |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Hide while the form it points at is on screen | `hideOver` |
|
|
12
|
+
| Stay off the hero until the hero CTA scrolls away | `showAfter` |
|
|
13
|
+
| Get out from under the Echo launcher | automatic |
|
|
14
|
+
| Ride out the iOS toolbar collapsing | `--sk-vv-layout-gap` |
|
|
15
|
+
| Get out of the way of the keyboard | `hideWhileTyping` |
|
|
16
|
+
| Tell Sonor which action converts | `cta_click` event |
|
|
17
17
|
|
|
18
18
|
## Use it
|
|
19
19
|
|
|
@@ -28,7 +28,7 @@ import { CtaBar, CtaBarAction } from '@sonordev/site-kit/cta-bar'
|
|
|
28
28
|
<main>{children}</main>
|
|
29
29
|
<Footer />
|
|
30
30
|
<CtaBar label="Call or request a quote" hideOver="#quote">
|
|
31
|
-
<CtaBarAction href="tel:+
|
|
31
|
+
<CtaBarAction href="tel:+15555550100" variant="secondary" icon={<Phone />}>
|
|
32
32
|
Call now
|
|
33
33
|
</CtaBarAction>
|
|
34
34
|
<CtaBarAction as={Link} href="/quote" icon={<ClipboardCheck />}>
|
package/src/forms/README.md
CHANGED
|
@@ -47,8 +47,8 @@ Three things hold for every experience:
|
|
|
47
47
|
the form still works and still submits.
|
|
48
48
|
- **Nobody pays for what they don't use.** Experience code is loaded on
|
|
49
49
|
demand, so a `classic` form downloads none of it.
|
|
50
|
-
- **Same engine.** Identical validation,
|
|
51
|
-
|
|
50
|
+
- **Same engine.** Identical validation, spam protection and CRM routing.
|
|
51
|
+
An experience is a rendering, not a fork.
|
|
52
52
|
|
|
53
53
|
### Editing what the form says back
|
|
54
54
|
|
|
@@ -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
|
|
@@ -202,13 +207,56 @@ Submissions auto-route based on `form_type`:
|
|
|
202
207
|
| `newsletter` | Email Subscribers | Newsletter signups |
|
|
203
208
|
| `custom` | Form Submissions only | Custom handling |
|
|
204
209
|
|
|
205
|
-
##
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
## Spam protection
|
|
211
|
+
|
|
212
|
+
Sonor screens every submission on its side, and there's nothing to configure.
|
|
213
|
+
It only accepts a submission with evidence that a real browser rendered the
|
|
214
|
+
page, which `<ManagedForm>` and `useForm` send automatically. A hand-rolled
|
|
215
|
+
`fetch` to the forms API, or a proxy through your own API route, can't send
|
|
216
|
+
it, so Sonor refuses those before anything is written: the visitor sees an
|
|
217
|
+
error and you get no lead. Submit through site-kit, and define the form's
|
|
218
|
+
fields in Sonor so both can render and validate them.
|
|
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. It's the browser's word, so it never changes how a submission is
|
|
246
|
+
checked. The agent is 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.
|
|
212
260
|
|
|
213
261
|
## Styles
|
|
214
262
|
|
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
|
@@ -315,7 +315,7 @@ Business description...
|
|
|
315
315
|
|
|
316
316
|
## Portfolio & Case Studies
|
|
317
317
|
|
|
318
|
-
### [Project Title](https://
|
|
318
|
+
### [Project Title](https://example.com/work/project)
|
|
319
319
|
|
|
320
320
|
Description of the project.
|
|
321
321
|
|
|
@@ -355,7 +355,7 @@ We follow a proven process...
|
|
|
355
355
|
|
|
356
356
|
### Family Law Basics
|
|
357
357
|
Topic: Family Law
|
|
358
|
-
Area:
|
|
358
|
+
Area: Springfield
|
|
359
359
|
Articles: 8
|
|
360
360
|
Service page: https://example.com/services/family-law
|
|
361
361
|
Pillar: [Complete Guide to Family Law](/article/family-law-guide)
|
|
@@ -515,7 +515,7 @@ export default function DivorcePage() {
|
|
|
515
515
|
title="Key Facts"
|
|
516
516
|
points={[
|
|
517
517
|
'25+ years of family law experience',
|
|
518
|
-
'Serving
|
|
518
|
+
'Serving Springfield and the surrounding counties',
|
|
519
519
|
'Free initial consultation available',
|
|
520
520
|
]}
|
|
521
521
|
speakable
|
package/src/mcp/README.md
CHANGED
|
@@ -82,11 +82,11 @@ endpoint and card (automatic in the build-time file once `/api/mcp` exists),
|
|
|
82
82
|
and `createProxy({ llmsDiscovery: { siteUrl, mcpServerCard: true } })` adds
|
|
83
83
|
`Link: <.../.well-known/mcp-server-card>; rel="service-desc"`.
|
|
84
84
|
|
|
85
|
-
### A custom MCP server
|
|
85
|
+
### A custom MCP server is left alone
|
|
86
86
|
|
|
87
87
|
The built-in tools are opt-in, never automatic. A site that runs its own MCP
|
|
88
|
-
server (its own tools, transport names or llms.txt section, like
|
|
89
|
-
|
|
88
|
+
server (its own tools, transport names or llms.txt section, like a
|
|
89
|
+
re-site-kit site) is a **custom implementation**, and site-kit keeps its
|
|
90
90
|
hands off:
|
|
91
91
|
|
|
92
92
|
| What | Built-in server | Custom server |
|
|
@@ -257,8 +257,8 @@ What the relay does, so you don't have to re-derive it:
|
|
|
257
257
|
|
|
258
258
|
The header and label default to `x-site-mcp-transport` and
|
|
259
259
|
`site-mcp-transport-v1`. A site with names already live passes the same
|
|
260
|
-
`{ header, label }` to all three factories (
|
|
261
|
-
`x-
|
|
260
|
+
`{ header, label }` to all three factories (for example
|
|
261
|
+
`x-example-mcp-transport` / `example-mcp-transport-v1`). This entry is Node
|
|
262
262
|
only and imports no `server-only`, so the plain-Node function can load it. It
|
|
263
263
|
is not re-exported from `@sonordev/site-kit/mcp`, which stays runtime-neutral.
|
|
264
264
|
|
|
@@ -333,8 +333,8 @@ single property check — not a request, and not a byte of page weight.
|
|
|
333
333
|
`<WebMcpTools>` registers thin wrappers that POST `tools/call` to this site's
|
|
334
334
|
own endpoint, so the in-page tool and the remote tool run the same server-side
|
|
335
335
|
handler. It also keeps `SONOR_API_KEY` out of the client bundle — registering
|
|
336
|
-
real handlers client-side would pull server code
|
|
337
|
-
|
|
336
|
+
real handlers client-side would pull server code, and the key with it, into a
|
|
337
|
+
`'use client'` graph and a public chunk.
|
|
338
338
|
|
|
339
339
|
Tools that genuinely need live DOM state go in `localTools` and run in-page.
|
|
340
340
|
|
package/src/motion/README.md
CHANGED
|
@@ -348,7 +348,7 @@ Per-site components map onto `<Reveal>` almost one to one:
|
|
|
348
348
|
| Site prop | `<Reveal>` |
|
|
349
349
|
|-----------|-----------|
|
|
350
350
|
| `from="up"`, `direction="up"` | `from="up"` |
|
|
351
|
-
| `y={30}`
|
|
351
|
+
| `y={30}` | `distance={30}` |
|
|
352
352
|
| `stagger` (boolean) | `stagger` |
|
|
353
353
|
| `delay`, `duration` | same, in seconds |
|
|
354
354
|
| `threshold`, `rootMargin` | `threshold` |
|
package/src/og/README.md
CHANGED
|
@@ -47,8 +47,8 @@ to child segments.** A card at `services/` is not inherited by
|
|
|
47
47
|
param), which the generator does automatically.
|
|
48
48
|
|
|
49
49
|
`sonor-setup og` and `sonor-setup doctor` both check this through one shared
|
|
50
|
-
rule (`og/wiring.ts`)
|
|
51
|
-
|
|
50
|
+
rule (`og/wiring.ts`), so the two never disagree. The rule reads the site's
|
|
51
|
+
code through
|
|
52
52
|
`shared/source-graph.ts`, which follows imports into `lib/` helpers and monorepo
|
|
53
53
|
workspace packages, so `twitter.card` set in a metadata helper counts. It used
|
|
54
54
|
to read the root layout alone.
|
|
@@ -62,16 +62,16 @@ zero-config seed for sites that have no config yet.
|
|
|
62
62
|
import { defineOgCard } from '@sonordev/site-kit/og'
|
|
63
63
|
|
|
64
64
|
export default defineOgCard({
|
|
65
|
-
theme: { bg: '#
|
|
66
|
-
fonts: [{ family: '
|
|
65
|
+
theme: { bg: '#0F172A', text: '#F8FAFC', accent: '#F59E0B', surface: '#1E293B' },
|
|
66
|
+
fonts: [{ family: 'Fraunces', weights: [800] }],
|
|
67
67
|
logo: '/logo-white.svg',
|
|
68
68
|
layout: 'split',
|
|
69
|
-
photo: { src: '/
|
|
69
|
+
photo: { src: '/crew.jpg' },
|
|
70
70
|
content: {
|
|
71
|
-
kicker: '
|
|
72
|
-
title: 'Built by\
|
|
73
|
-
subtitle: 'Designed, built
|
|
74
|
-
bar: ['Free
|
|
71
|
+
kicker: 'Remodeling · Springfield',
|
|
72
|
+
title: 'Built by\nneighbors',
|
|
73
|
+
subtitle: 'Designed, built and installed by one local crew.',
|
|
74
|
+
bar: ['Free estimate', 'example.com'],
|
|
75
75
|
},
|
|
76
76
|
})
|
|
77
77
|
```
|
|
@@ -86,10 +86,10 @@ The title opens at 104px and the renderer steps it down until it fits **both**
|
|
|
86
86
|
axes, stopping at a 56px legibility floor — below that a headline stops reading
|
|
87
87
|
at the ~300px thumbnail width platforms actually show.
|
|
88
88
|
|
|
89
|
-
If copy still
|
|
90
|
-
element and the overflow in pixels. That
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
If copy still doesn't fit at the floor, the command **fails** and names the
|
|
90
|
+
element and the overflow in pixels. That's deliberate: otherwise a card can ship
|
|
91
|
+
with the kicker off-canvas and the subtitle buried under the bottom bar while
|
|
92
|
+
the CLI prints a tick.
|
|
93
93
|
|
|
94
94
|
Rules of thumb: about 10 uppercase characters per title line, and about 29 for
|
|
95
95
|
the kicker. `\n` in a title is a hard break, so choose the wrap yourself rather
|
|
@@ -106,8 +106,8 @@ Hand-write the few that deserve it:
|
|
|
106
106
|
|
|
107
107
|
```ts
|
|
108
108
|
cards: {
|
|
109
|
-
'/free-
|
|
110
|
-
content: { kicker: 'Free
|
|
109
|
+
'/free-estimate': {
|
|
110
|
+
content: { kicker: 'Free estimate', title: 'Know the\ncost first' },
|
|
111
111
|
photo: { src: '/lp/hero.jpg' },
|
|
112
112
|
},
|
|
113
113
|
},
|
|
@@ -119,8 +119,8 @@ so an entry only states what differs.
|
|
|
119
119
|
Managed titles carry the brand for the SERP (`About Us | Acme`); the card drops
|
|
120
120
|
it. It also drops the two broken suffixes managed titles turn up with: a dangling
|
|
121
121
|
separator with no brand after it (`About Us |`) and a domain after a comma
|
|
122
|
-
(`Privacy Policy,
|
|
123
|
-
separator (`Walk-In
|
|
122
|
+
(`Privacy Policy, example.com`). A hyphen inside a word is never a
|
|
123
|
+
separator (`Walk-In Showers` stays whole).
|
|
124
124
|
|
|
125
125
|
### Dynamic routes are keyed by pattern, one `*` per level
|
|
126
126
|
|
|
@@ -141,23 +141,22 @@ cards: {
|
|
|
141
141
|
},
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
service-by-metro pages
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
The generator keys each depth separately, so a `/services/*/*` entry reaches
|
|
145
|
+
the service-by-metro pages and nothing else. A `cards` key that matches no
|
|
146
|
+
rendered route is reported by `sonor-setup og` (`og.cards`, a warning), since
|
|
147
|
+
otherwise it would match nothing, silently.
|
|
148
148
|
|
|
149
149
|
> **Writing a nested pattern in a comment.** `/services/*/*` contains `*/`,
|
|
150
150
|
> which **closes a `/* */` block comment early**. In TypeScript put it in a
|
|
151
|
-
> string, a `//` line comment, or spell the depth out in prose
|
|
152
|
-
>
|
|
151
|
+
> string, a `//` line comment, or spell the depth out in prose. It's an easy
|
|
152
|
+
> slip in exactly the comment that explains the pattern.
|
|
153
153
|
|
|
154
154
|
### Static routes under a dynamic segment get a card too
|
|
155
155
|
|
|
156
156
|
`properties/[slug]/about` is a static route under a dynamic one. It gets its
|
|
157
157
|
own card, keyed `/properties/*/about` and titled from its own name ("About",
|
|
158
158
|
kicker "properties"). The generator used to stop at the first dynamic segment,
|
|
159
|
-
|
|
160
|
-
property sites.
|
|
159
|
+
which left these pages with no og:image at all.
|
|
161
160
|
|
|
162
161
|
### Per-URL cards: one per floor plan, one per community
|
|
163
162
|
|
|
@@ -213,7 +212,7 @@ The URL ends in `.jpg`, so a `trailingSlash: true` site never redirects it.
|
|
|
213
212
|
Per-URL cards need `sharp` (they have to be `.jpg`); without it the command
|
|
214
213
|
fails those cards and says so. Their copy comes from Sonor's managed title for
|
|
215
214
|
that URL, like any route card. A per-URL key matches a pattern one segment per
|
|
216
|
-
`*`, so `/properties/
|
|
215
|
+
`*`, so `/properties/maple-court/about` falls under `/properties/*/about`.
|
|
217
216
|
|
|
218
217
|
Cards are re-encoded to JPEG when `sharp` resolves — on a real 18-card site that
|
|
219
218
|
was 5.6 MB → 1.4 MB. Without sharp they stay PNG, which is correct, just heavier.
|
|
@@ -249,7 +248,7 @@ export default createOgImage<{ slug: string }>(async ({ slug }) => {
|
|
|
249
248
|
kicker: 'From the publication',
|
|
250
249
|
title: post.title,
|
|
251
250
|
photoUrl: post.featured_image, // must be absolute
|
|
252
|
-
bar: '
|
|
251
|
+
bar: 'example.com',
|
|
253
252
|
}
|
|
254
253
|
})
|
|
255
254
|
```
|
package/src/proxy/README.md
CHANGED
|
@@ -114,7 +114,7 @@ into a dynamic one. The resolver is deprecated.
|
|
|
114
114
|
| Header | Value |
|
|
115
115
|
|--------|-------|
|
|
116
116
|
| X-DNS-Prefetch-Control | `on` |
|
|
117
|
-
| Content-Security-Policy | `frame-ancestors
|
|
117
|
+
| Content-Security-Policy | `frame-ancestors` from `DEFAULT_FRAME_ANCESTORS` (below) |
|
|
118
118
|
| X-Content-Type-Options | `nosniff` |
|
|
119
119
|
| X-XSS-Protection | `1; mode=block` |
|
|
120
120
|
| Referrer-Policy | `strict-origin-when-cross-origin` |
|
|
@@ -124,23 +124,23 @@ Note there is no `X-Frame-Options` row — that is deliberate, see below.
|
|
|
124
124
|
|
|
125
125
|
### Framing / `frame-ancestors`
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
Managed sites ship a CSP `frame-ancestors` allowlist instead of
|
|
128
|
+
`X-Frame-Options`. The default, `DEFAULT_FRAME_ANCESTORS`, is `'self'`,
|
|
129
|
+
Sonor's dashboard (`https://app.sonor.io`, for Edit on page) and the Upforge
|
|
130
|
+
showcase origins (`upforge.io`, `upforgelabs.com` and their subdomains), where
|
|
131
|
+
live client sites appear in portfolio frames. XFO has no allowlist form
|
|
132
|
+
(`ALLOW-FROM` is dead), and emitting both would let a stray `DENY` silently
|
|
133
|
+
re-block the embed, so `X-Frame-Options` is **omitted** whenever
|
|
134
|
+
`frameAncestors` is active. Every origin not on the list is still blocked.
|
|
135
|
+
|
|
136
|
+
`frameAncestors` replaces the default, so spread it to add an origin:
|
|
133
137
|
|
|
134
138
|
```ts
|
|
139
|
+
import { createProxy, DEFAULT_FRAME_ANCESTORS } from '@sonordev/site-kit/proxy'
|
|
140
|
+
|
|
135
141
|
createProxy({
|
|
136
|
-
// extend the allowlist
|
|
137
142
|
securityHeaders: {
|
|
138
|
-
frameAncestors: [
|
|
139
|
-
"'self'",
|
|
140
|
-
'https://upforge.io',
|
|
141
|
-
'https://*.upforge.io',
|
|
142
|
-
'https://partner.example.com',
|
|
143
|
-
],
|
|
143
|
+
frameAncestors: [...DEFAULT_FRAME_ANCESTORS, 'https://partner.example.com'],
|
|
144
144
|
},
|
|
145
145
|
})
|
|
146
146
|
|