@sonordev/site-kit 8.6.0 → 8.8.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/CHANGELOG.md +17 -0
- package/agent-manifest.json +1 -1
- package/dist/{AnalyticsProvider-GVJYL6XY.js → AnalyticsProvider-7SBJ27SI.js} +6 -6
- package/dist/{ArticleViewTracker-AOJXEBYM.js → ArticleViewTracker-PV2RD3P6.js} +5 -5
- package/dist/{BlocksPopup-AJ3CKVQG.js → BlocksPopup-NEEFV2L3.js} +9 -8
- package/dist/{ChatWidget-EYBVB5SZ.js → ChatWidget-V46UBDOM.js} +5 -5
- package/dist/{FileField-UEQYIFRO.js → FileField-MJ3RCNFJ.js} +3 -3
- package/dist/{FormSpotlight-H73ZZ66T.js → FormSpotlight-NQPQFZS7.js} +1 -1
- package/dist/{FormStage-2PLWWYBW.js → FormStage-4T5PJR5K.js} +1 -1
- package/dist/{ManagedForm-ZQOFXEWS.js → ManagedForm-DDDVMKLJ.js} +6 -6
- package/dist/{ManagedNewsletterForm-5TE47YZD.js → ManagedNewsletterForm-TUYGN6O3.js} +9 -9
- package/dist/{SignalCore-FEFNMNYN.js → SignalCore-OW3YIH6M.js} +5 -5
- package/dist/SiteChat-XMKHBSEK.js +6 -0
- package/dist/SiteDesignReporter-BRH7ORQS.js +13 -0
- package/dist/{SitePopups-C44YTQ3Q.js → SitePopups-VU2C3Z6B.js} +6 -6
- package/dist/SitemapSync-C4H3HF3L.js +9 -0
- package/dist/_client/booking-widget.js +5 -5
- package/dist/admin-auth/index.d.ts +2 -2
- package/dist/admin-auth/session.d.ts +2 -2
- package/dist/affiliates/index.js +3 -3
- package/dist/analytics/content-text.d.ts +17 -0
- package/dist/analytics/index.js +6 -6
- package/dist/{article-styles-VASFBYA3.js → article-styles-D7VCDXMG.js} +2 -1
- package/dist/article-tables-W45BE353.js +3 -0
- package/dist/articles/index.js +7 -20
- package/dist/articles/server-ui.js +29 -55
- package/dist/articles/server.js +6 -5
- package/dist/chat/index.js +8 -8
- package/dist/{chunk-3RDJKIYO.js → chunk-2WDWQI7K.js} +1 -1
- package/dist/{chunk-KTMHRXDT.js → chunk-2WM4TNDF.js} +1 -1
- package/dist/chunk-3MJNBOUO.js +8 -0
- package/dist/{chunk-BF7TZYC3.js → chunk-52KO4LKD.js} +1 -1
- package/dist/chunk-64XODP7M.js +7 -0
- package/dist/{chunk-DSD3XZQQ.js → chunk-6BFQI3ZE.js} +3 -3
- package/dist/{chunk-QNOJSSQT.js → chunk-6DXQHNZK.js} +1 -1
- package/dist/{chunk-5D7ZISUP.js → chunk-7DYMM6SS.js} +11 -6
- package/dist/{chunk-QQT2T6JQ.js → chunk-7HW4TZB5.js} +1 -1
- package/dist/{chunk-E4PTMORJ.js → chunk-AL5UCWLI.js} +23 -8
- package/dist/chunk-AYQEIMGS.js +390 -0
- package/dist/{chunk-L6QTERSH.js → chunk-DYROJGI2.js} +1 -1
- package/dist/{chunk-XA7Q2VQA.js → chunk-EK7P62DT.js} +1 -1
- package/dist/{chunk-BUE3Z32O.js → chunk-F6WLHVMQ.js} +1 -1
- package/dist/{chunk-IYCSDFPN.js → chunk-FLOJUMFR.js} +2 -2
- package/dist/{chunk-7R2STOVD.js → chunk-G7BRCSV6.js} +3 -3
- package/dist/{chunk-NI6ZTP3D.js → chunk-GQN4BGK6.js} +1 -1
- package/dist/{chunk-GFY3E7EQ.js → chunk-GXWM5KNS.js} +15 -7
- package/dist/{chunk-U34O6I7W.js → chunk-HN7GVGWC.js} +3 -3
- package/dist/{chunk-6KU7STM3.js → chunk-HY7ZEU2C.js} +3 -1
- package/dist/chunk-IBHBEUZV.js +27 -0
- package/dist/{chunk-ZZSAWXWD.js → chunk-IDC5CVBR.js} +1 -1
- package/dist/{chunk-HMTPRBYJ.js → chunk-IWNJCRXW.js} +1 -1
- package/dist/chunk-JESLPEST.js +14 -0
- package/dist/{chunk-BC7JMY4Z.js → chunk-JG53YJLM.js} +58 -1
- package/dist/{chunk-RI2N2EBI.js → chunk-JYITKUUB.js} +13 -5
- package/dist/{chunk-V67OD7RW.js → chunk-KG5IEV7I.js} +113 -93
- package/dist/chunk-KIYACQW3.js +1 -0
- package/dist/{chunk-JSW42BHL.js → chunk-M5JIHQ42.js} +1 -1
- package/dist/{chunk-YJH6WE76.js → chunk-NFGAXWCN.js} +9 -9
- package/dist/{chunk-Y35BWW2C.js → chunk-ODOV76VW.js} +1 -1
- package/dist/{chunk-VA6IJ55K.js → chunk-OPUY4RS6.js} +1 -1
- package/dist/{chunk-6NXFA7HE.js → chunk-OWZWRP7Y.js} +2 -2
- package/dist/{chunk-JJ4JS3LD.js → chunk-PAYCK7FC.js} +1 -1
- package/dist/{chunk-72F44HKL.js → chunk-PSS6M4CG.js} +1 -1
- package/dist/{chunk-O3Z5HHFP.js → chunk-SBPUFTMP.js} +2 -2
- package/dist/{chunk-OFSV5TZY.js → chunk-SHAUX2MQ.js} +1 -1
- package/dist/{chunk-OWO3RFEX.js → chunk-SLFYTELD.js} +1 -1
- package/dist/{chunk-CMUD37WV.js → chunk-SVHPILS3.js} +1 -1
- package/dist/{chunk-YNBRVVTX.js → chunk-THXELZTQ.js} +1 -1
- package/dist/{chunk-IGAIQ2B3.js → chunk-TP32MRXV.js} +2 -2
- package/dist/{chunk-NUXXFCIT.js → chunk-URLTBVRX.js} +4 -4
- package/dist/chunk-XAZC2TUD.js +1119 -0
- package/dist/{chunk-URJN75ZG.js → chunk-XIDWNXZN.js} +3 -3
- package/dist/chunk-XLAHRN4B.js +1 -0
- package/dist/chunk-XS2ORMUG.js +32 -0
- package/dist/{chunk-6AWIUO2Z.js → chunk-Y6SHFILE.js} +1 -1
- package/dist/{chunk-FTSEP7Q3.js → chunk-YEX5RAEU.js} +3 -3
- package/dist/{chunk-AJG74ZZV.js → chunk-YRQS7VAH.js} +1 -1
- package/dist/{chunk-DLM4FFNH.js → chunk-ZQSCMYZO.js} +1 -1
- package/dist/client/index.js +4 -4
- package/dist/contracts/campaign-normalize.d.ts +2 -0
- package/dist/contracts/entries.d.ts +1 -1
- package/dist/contracts/frame-ready.d.ts +23 -0
- package/dist/contracts/funnels.d.ts +576 -0
- package/dist/contracts/inbox.d.ts +42 -0
- package/dist/contracts/llms.d.ts +200 -0
- package/dist/contracts/portfolio.d.ts +361 -8
- package/dist/contracts/publication-branding.d.ts +10 -2
- package/dist/contracts/site-cache.d.ts +2 -0
- package/dist/fleet/index.js +6 -6
- package/dist/forms/index.js +9 -9
- package/dist/forms/server.js +2 -2
- package/dist/images/index.js +4 -4
- package/dist/index.js +1 -1
- package/dist/landing/contract.d.ts +2 -13
- package/dist/landing/contract.js +2 -0
- package/dist/landing/index.js +4 -2
- package/dist/landing/managed-server.d.ts +21 -0
- package/dist/landing/managed.d.ts +11 -0
- package/dist/landing/metadata.js +2 -0
- package/dist/landing/server.d.ts +5 -0
- package/dist/landing/server.js +20 -2
- package/dist/layout/client.js +9 -9
- package/dist/layout/index.js +10 -10
- package/dist/llms/contract.js +2 -1
- package/dist/llms/index.js +9 -7
- package/dist/llms/merge.d.ts +29 -0
- package/dist/maps/index.js +3 -3
- package/dist/mcp/sonor.js +9 -8
- package/dist/og/pages.d.ts +3 -3
- package/dist/portfolio/contract.js +705 -1
- package/dist/press-kit/articles/article-body.d.ts +12 -0
- package/dist/press-kit/articles/article-heading-ids.d.ts +7 -0
- package/dist/press-kit/articles/article-headings.d.ts +61 -0
- package/dist/press-kit/articles/html-tags.d.ts +46 -0
- package/dist/press-kit/articles/types.d.ts +2 -0
- package/dist/revalidate/index.js +3 -3
- package/dist/runtime/index.js +3 -3
- package/dist/seo/client.js +6 -6
- package/dist/seo/index.js +14 -13
- package/dist/seo/llms/contract.js +2 -1
- package/dist/seo/llms.js +9 -7
- package/dist/seo/meta/contract.js +2 -1
- package/dist/seo/register-sitemap-cli.js +1 -1
- package/dist/seo/server.js +5 -4
- package/dist/seo/sitemap.js +7 -5
- package/dist/seo-meta/contract.js +2 -1
- package/dist/server/index.js +3 -3
- package/dist/{server-api-EGOKC7Q3.js → server-api-XIKDWSNO.js} +2 -2
- package/dist/shared/build-entries.d.ts +3 -1
- package/dist/shared/frame.d.ts +1 -1
- package/dist/shared/markdown.d.ts +7 -6
- package/dist/shared/source-graph.d.ts +5 -5
- package/dist/shared/version.d.ts +1 -1
- package/dist/signal/index.js +2 -2
- package/dist/sitemap/index.js +7 -5
- package/dist/sitemap/shared.d.ts +1 -2
- package/dist/slots/index.js +2 -2
- package/dist/sync/index.js +5 -5
- package/dist/website/contract.js +2 -1
- package/dist/website/images.js +4 -4
- package/dist/website/index.js +10 -9
- package/dist/website/landing/server.js +20 -2
- package/dist/website/landing.js +4 -2
- package/dist/website/popups.js +9 -8
- package/dist/website/slots.js +2 -2
- package/dist/{writeLLMsTxt-KHVHNSDM.js → writeLLMsTxt-4HHUAOA6.js} +6 -4
- package/docs.json +5 -0
- package/package.json +1 -1
- package/src/analytics/README.md +1 -1
- package/src/landing/README.md +64 -0
- package/src/llms/README.md +52 -20
- package/dist/SiteChat-3MKYL2XM.js +0 -6
- package/dist/SiteDesignReporter-AJDIBNHI.js +0 -12
- package/dist/SitemapSync-UJA6JVPW.js +0 -9
- package/dist/article-tables-6XHVULPU.js +0 -2
- package/dist/chunk-EKBEOXTH.js +0 -114
- package/dist/chunk-NZ4RXN3G.js +0 -145
- package/dist/chunk-OSYCOC3K.js +0 -21
- /package/dist/{chunk-7ZWO5HDQ.js → chunk-BTHEQDN4.js} +0 -0
- /package/dist/{chunk-BQR4IPBW.js → chunk-ISRBSTB7.js} +0 -0
- /package/dist/{chunk-IHG36STL.js → chunk-XHYE24HU.js} +0 -0
package/dist/contracts/llms.d.ts
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* `@sonordev/site-kit/seo/llms/contract`.
|
|
7
7
|
*
|
|
8
8
|
* @see LLM_GEO_CONTRACT.md in this folder
|
|
9
|
+
*
|
|
10
|
+
* It is also where llms.txt's page notes, page titles and page order are
|
|
11
|
+
* decided, so the Sonor API's index, its fallback file and site-kit's local
|
|
12
|
+
* generator can't disagree: `pickLlmsPageDescription` (summary, curated meta,
|
|
13
|
+
* the page's own meta, the schema description, then a cleaned scrape),
|
|
14
|
+
* `llmsPageTitle`, `isLlmsListablePage`, `selectLlmsPages`, `groupLlmsPages`,
|
|
15
|
+
* `llmsPageListNote` and `llmsContactLines`.
|
|
9
16
|
*/
|
|
10
17
|
/** Increment when public LLMs payload shape changes incompatibly. */
|
|
11
18
|
export declare const LLM_GEO_CONTRACT_VERSION: 1;
|
|
@@ -29,8 +36,27 @@ export interface LLMsPayloadMeta {
|
|
|
29
36
|
llms_disclaimer?: string | null;
|
|
30
37
|
}
|
|
31
38
|
export declare function clipAtWordBoundary(text: string, max: number): string;
|
|
39
|
+
/**
|
|
40
|
+
* The text with its phone numbers and email addresses taken out. The business's
|
|
41
|
+
* Contact Information block already carries them, and a page's note is no place
|
|
42
|
+
* for either.
|
|
43
|
+
*
|
|
44
|
+
* Works a sentence at a time: a sentence with a detail in it keeps what comes
|
|
45
|
+
* before the detail (and before a "call" or "email us at" in front of it) when
|
|
46
|
+
* that is still a sentence of four words or more, and is dropped otherwise.
|
|
47
|
+
* "Family-owned HVAC in Cincinnati, call 513-555-1234 today." becomes
|
|
48
|
+
* "Family-owned HVAC in Cincinnati."; "Call 513-555-1234 for a quote." goes.
|
|
49
|
+
* Cutting out only the number would leave "for a quote." behind.
|
|
50
|
+
*/
|
|
51
|
+
export declare function stripContactDetails(text: string): string;
|
|
52
|
+
export declare function stripPageFiller(note: string): string;
|
|
32
53
|
/**
|
|
33
54
|
* Public-safe summary for llms.txt `[title](url): notes` — never CRM-only or internal copy.
|
|
55
|
+
*
|
|
56
|
+
* A summary that mentions a phone number or an email address loses that part
|
|
57
|
+
* (`stripContactDetails`) and is still used. It used to be thrown away whole,
|
|
58
|
+
* and a page with no summary is described by its scrape, so a well-meant
|
|
59
|
+
* "...call 555-1234" summary made the page's note worse, not safer.
|
|
34
60
|
*/
|
|
35
61
|
export declare function sanitizeLlmsPublicSummary(raw: unknown): string | null;
|
|
36
62
|
/**
|
|
@@ -53,3 +79,177 @@ export declare function sanitizePrimaryLanguageTag(raw: unknown): string | null;
|
|
|
53
79
|
export declare function isEmittableReviewNode(node: Record<string, unknown>): boolean;
|
|
54
80
|
/** Pick JSON-LD-safe subset of managed_llm_schema for script injection. */
|
|
55
81
|
export declare function pickManagedLlmSchemaForJsonLd(schema: Record<string, unknown> | null | undefined): Record<string, unknown> | null;
|
|
82
|
+
/** The longest note a page line carries. */
|
|
83
|
+
export declare const LLMS_DESCRIPTION_MAX_LENGTH = 500;
|
|
84
|
+
/**
|
|
85
|
+
* A page's scraped text (seo_pages.content_text) made fit to describe it.
|
|
86
|
+
*
|
|
87
|
+
* The DOM scrape joins elements with nothing between them: a heading, its
|
|
88
|
+
* tagline and the first paragraph arrive as "Getting paid for the
|
|
89
|
+
* workIncome, spending, budgets.Track expenses by vendor", a contact page as
|
|
90
|
+
* "First Name*Last Name*Email*PhoneMessage*Send Message". This puts the
|
|
91
|
+
* whitespace back, drops what is not about the page (form labels, menu items,
|
|
92
|
+
* skip links, cookie banners, "Eyebrow · Section" lines) and returns what is
|
|
93
|
+
* left. It may return '' when nothing worth a note is.
|
|
94
|
+
*
|
|
95
|
+
* It does not cut to a length: the caller clips (`clipAtWordBoundary`). It
|
|
96
|
+
* does not strip phone numbers: that is `stripContactDetails`. `knownTerms`
|
|
97
|
+
* are words written with a capital inside them that must stay whole.
|
|
98
|
+
*/
|
|
99
|
+
export declare function cleanScrapedPageText(raw: string | null | undefined, options?: {
|
|
100
|
+
knownTerms?: readonly string[];
|
|
101
|
+
}): string;
|
|
102
|
+
/** Where a page's note came from, best source first. */
|
|
103
|
+
export type LlmsDescriptionSource = 'summary' | 'managed_meta' | 'meta' | 'schema' | 'scrape';
|
|
104
|
+
export interface LlmsDescriptionInput {
|
|
105
|
+
/** The page's own name: a note that only repeats it says nothing. */
|
|
106
|
+
title?: string | null;
|
|
107
|
+
/** seo_pages.llms_public_summary: written for this file. */
|
|
108
|
+
llms_public_summary?: string | null;
|
|
109
|
+
/** seo_pages.managed_meta_description: the curated meta description. */
|
|
110
|
+
managed_meta_description?: string | null;
|
|
111
|
+
/** seo_pages.meta_description: the meta description the page serves. */
|
|
112
|
+
meta_description?: string | null;
|
|
113
|
+
/** The `description` of the page's managed LLM schema. */
|
|
114
|
+
llm_schema_description?: string | null;
|
|
115
|
+
/** seo_pages.content_text: the DOM scrape. */
|
|
116
|
+
content_text?: string | null;
|
|
117
|
+
/**
|
|
118
|
+
* True when the page's managed copy says the page itself is missing
|
|
119
|
+
* (describesErrorPage). That copy, the schema and the page's own meta
|
|
120
|
+
* description are then not used.
|
|
121
|
+
*/
|
|
122
|
+
copyWithheld?: boolean;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The note a page's line in llms.txt carries, and where it came from.
|
|
126
|
+
*
|
|
127
|
+
* One rule for every llms.txt Sonor builds, best source first:
|
|
128
|
+
*
|
|
129
|
+
* 1. `llms_public_summary`, written for this file, with a phone number or an
|
|
130
|
+
* email address taken out rather than the whole summary thrown away;
|
|
131
|
+
* 2. `managed_meta_description`, the curated meta description;
|
|
132
|
+
* 3. `meta_description`, the one the page serves;
|
|
133
|
+
* 4. the managed schema's description;
|
|
134
|
+
* 5. the cleaned scrape (`cleanScrapedPageText`), last, because it is the
|
|
135
|
+
* least written of the five.
|
|
136
|
+
*
|
|
137
|
+
* Until 2026-10 the scrape was FIRST: llms.txt, the one file handed straight
|
|
138
|
+
* to AI crawlers, described a contact page as "First Name*Last Name*Email*".
|
|
139
|
+
* Every note is cut at a sentence or word boundary at 500 characters. A scrape
|
|
140
|
+
* that is an error page is not used (`usablePageText`). Returns null when
|
|
141
|
+
* nothing says anything about the page.
|
|
142
|
+
*/
|
|
143
|
+
export declare function pickLlmsPageDescription(input: LlmsDescriptionInput): {
|
|
144
|
+
text: string;
|
|
145
|
+
source: LlmsDescriptionSource;
|
|
146
|
+
} | null;
|
|
147
|
+
/** `pickLlmsPageDescription`'s text. */
|
|
148
|
+
export declare function resolveLlmsPageDescription(input: LlmsDescriptionInput): string | null;
|
|
149
|
+
/**
|
|
150
|
+
* The note on a page's line, from what the payload carries: its
|
|
151
|
+
* `llms_public_summary`, then its `description`. With `publicSummaryOnly`
|
|
152
|
+
* (a site that wants strict notes) only the summary counts. Single source of
|
|
153
|
+
* truth for the Sonor API's fallback file and site-kit's local generator, which
|
|
154
|
+
* each had their own copy.
|
|
155
|
+
*/
|
|
156
|
+
export declare function llmsPageListNote(page: {
|
|
157
|
+
description?: string | null;
|
|
158
|
+
llms_public_summary?: string | null;
|
|
159
|
+
}, publicSummaryOnly?: boolean): string | undefined;
|
|
160
|
+
export interface LlmsListablePage {
|
|
161
|
+
path: string;
|
|
162
|
+
http_status?: number | null;
|
|
163
|
+
has_noindex?: boolean | null;
|
|
164
|
+
robots_meta?: string | null;
|
|
165
|
+
managed_robots_meta?: string | null;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Whether a live seo_pages row belongs in llms.txt. A page the last visit saw
|
|
169
|
+
* answer with an error (404, 5xx), a page that asks search engines to leave it
|
|
170
|
+
* out, and a utility page (a thank-you page, a cart, a login) are not listed:
|
|
171
|
+
* llms.txt is the index of what a site is, and every dead or unwanted link in
|
|
172
|
+
* it is one a model may follow and cite. Retired pages never get here
|
|
173
|
+
* (LIVE_SEO_PAGE_FILTER).
|
|
174
|
+
*/
|
|
175
|
+
export declare function isLlmsListablePage(page: LlmsListablePage): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* What a page is called in llms.txt: its curated title, else the title it
|
|
178
|
+
* serves, else its h1, each with the brand suffix off and only when it is a
|
|
179
|
+
* name and not an address; the home page is `homeName`; else the last segment
|
|
180
|
+
* of its path as a label. Never the path itself: "[/features/accounting]" was
|
|
181
|
+
* what a page with no title got.
|
|
182
|
+
*/
|
|
183
|
+
export declare function llmsPageTitle(page: {
|
|
184
|
+
path: string;
|
|
185
|
+
managed_title?: string | null;
|
|
186
|
+
title?: string | null;
|
|
187
|
+
h1?: string | null;
|
|
188
|
+
}, homeName?: string | null): string;
|
|
189
|
+
/**
|
|
190
|
+
* Choose which pages survive a page cap.
|
|
191
|
+
*
|
|
192
|
+
* A round-robin across top-level sections, not a truncation: cutting an
|
|
193
|
+
* alphabetical list amputates its tail. On a 191-page site a cap of 50 kept 37
|
|
194
|
+
* `/industries/*` pages and dropped `/work`, `/pricing`, `/services`,
|
|
195
|
+
* `/web-design` and `/resources` entirely. Every section is represented at any
|
|
196
|
+
* cap, and the survivors keep their original order, because the round-robin
|
|
197
|
+
* decides membership, not presentation. Sections are taken most valuable first
|
|
198
|
+
* (the order `groupLlmsPages` lists them in, legal pages last), so a cap smaller
|
|
199
|
+
* than the number of sections drops the least valuable one.
|
|
200
|
+
*
|
|
201
|
+
* Single source of truth: the Sonor API caps its index with it, and site-kit's
|
|
202
|
+
* local generator caps with it instead of `slice(0, max)`.
|
|
203
|
+
*/
|
|
204
|
+
export declare function selectLlmsPages<T extends {
|
|
205
|
+
path: string;
|
|
206
|
+
}>(pages: T[], cap: number): T[];
|
|
207
|
+
export interface LlmsPageGroup<T> {
|
|
208
|
+
/** null for the one list of a small site. */
|
|
209
|
+
heading: string | null;
|
|
210
|
+
pages: T[];
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The page index in the order and groups an answer engine reads best: the
|
|
214
|
+
* home page, then the site's top-level pages by value (what it does, where,
|
|
215
|
+
* the proof, who, the cost, contact), then one group per section of the site
|
|
216
|
+
* (`/services/*` is "Services"), biggest unlisted sections last, and the legal
|
|
217
|
+
* pages at the very end. Nothing is dropped or renamed; a list of 12 pages or
|
|
218
|
+
* fewer is one ungrouped list in the same order.
|
|
219
|
+
*
|
|
220
|
+
* Single source of truth for the Sonor API's fallback file and site-kit's
|
|
221
|
+
* local generator.
|
|
222
|
+
*/
|
|
223
|
+
export declare function groupLlmsPages<T extends {
|
|
224
|
+
path: string;
|
|
225
|
+
}>(pages: readonly T[]): Array<LlmsPageGroup<T>>;
|
|
226
|
+
/**
|
|
227
|
+
* The one-line summary under the business name: its tagline, else the first
|
|
228
|
+
* sentence of its description, else its name. The first sentence comes from
|
|
229
|
+
* `splitSentences`, not `description.split('.')[0]`, which ends "Smith &
|
|
230
|
+
* Sons, Inc. is a roofing company" at "Inc".
|
|
231
|
+
*/
|
|
232
|
+
export declare function llmsBlockquoteSummary(business: {
|
|
233
|
+
name?: string | null;
|
|
234
|
+
tagline?: string | null;
|
|
235
|
+
description?: string | null;
|
|
236
|
+
}): string;
|
|
237
|
+
export interface LlmsContactLike {
|
|
238
|
+
phone?: string | null;
|
|
239
|
+
email?: string | null;
|
|
240
|
+
address?: string | null;
|
|
241
|
+
city?: string | null;
|
|
242
|
+
state?: string | null;
|
|
243
|
+
postal_code?: string | null;
|
|
244
|
+
country?: string | null;
|
|
245
|
+
hours?: unknown;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The bullets of the Contact Information block, none for a field that is
|
|
249
|
+
* empty. An address reads "1 Main St, Springfield, OH 45501", and a
|
|
250
|
+
* default "US" is not printed. Empty array when there is nothing to show, so a
|
|
251
|
+
* caller leaves the whole section out rather than print a bare heading.
|
|
252
|
+
*/
|
|
253
|
+
export declare function llmsContactLines(contact: LlmsContactLike | null | undefined): string[];
|
|
254
|
+
/** A page or service link as an absolute URL on the site, so a crawler never resolves it against llms.txt's own address. */
|
|
255
|
+
export declare function llmsAbsoluteUrl(baseUrl: string | null | undefined, pathOrUrl: string): string;
|
|
@@ -16,16 +16,369 @@
|
|
|
16
16
|
* must carry, and which numbers may headline. The Sonor API applies the same
|
|
17
17
|
* rules.
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
export declare const FRAME_READY_MESSAGE: 'sonor:frame-ready';
|
|
21
|
-
export interface FrameReadyMessage {
|
|
22
|
-
type: typeof FRAME_READY_MESSAGE;
|
|
23
|
-
v: 1;
|
|
24
|
-
}
|
|
25
|
-
/** True for a {@link FrameReadyMessage}, for the embedder's message handler. */
|
|
26
|
-
export declare function isFrameReadyMessage(data: unknown): data is FrameReadyMessage;
|
|
19
|
+
export { FRAME_READY_MESSAGE, isFrameReadyMessage, type FrameReadyMessage } from './frame-ready';
|
|
27
20
|
/** Bump on breaking changes to the collapse policy. */
|
|
28
21
|
export declare const PORTFOLIO_CONTRACT_VERSION: 1;
|
|
22
|
+
/** The four beats, in reading order. */
|
|
23
|
+
export declare const CASE_STUDY_STAGES: readonly ['problem', 'build', 'proof', 'outcome'];
|
|
24
|
+
export type CaseStudyStage = (typeof CASE_STUDY_STAGES)[number];
|
|
25
|
+
/** The generic name of each beat and the question its sections answer. */
|
|
26
|
+
export declare const CASE_STUDY_STAGE_COPY: Record<CaseStudyStage, {
|
|
27
|
+
name: string;
|
|
28
|
+
question: string;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* A site's own names for the four stages ("Heat", "Shape", "Temper",
|
|
32
|
+
* "Finish"), set per project in the dashboard (branding is per project) and
|
|
33
|
+
* read everywhere a stage is named: the editor, the public portfolio config,
|
|
34
|
+
* the site itself. A stage left unnamed falls back to its generic name. The
|
|
35
|
+
* ids never change; only what people see does.
|
|
36
|
+
*/
|
|
37
|
+
export type CaseStudyStageNames = Partial<Record<CaseStudyStage, string>>;
|
|
38
|
+
/** The longest a stage name may run. It's a heading word, a rail label, a tab. */
|
|
39
|
+
export declare const STAGE_NAME_MAX_CHARS = 24;
|
|
40
|
+
/**
|
|
41
|
+
* What's wrong with a set of stage names, or an empty list. Absent passes
|
|
42
|
+
* (generic names); present must be an object of short text keyed by stage.
|
|
43
|
+
*/
|
|
44
|
+
export declare function stageNamesProblems(names: unknown): string[];
|
|
45
|
+
/** The name a stage is shown under: the site's own when set, else the generic one. */
|
|
46
|
+
export declare function stageName(names: CaseStudyStageNames | null | undefined, stage: CaseStudyStage): string;
|
|
47
|
+
/**
|
|
48
|
+
* Every section type a case study can hold, in canonical reading order:
|
|
49
|
+
* hero, then each stage's sections, then the two that aren't rendered as
|
|
50
|
+
* sections of the story (SEO metadata, the closing CTA). This is THE list;
|
|
51
|
+
* the Sonor API and agency-site-kit import it rather than keeping their own.
|
|
52
|
+
*/
|
|
53
|
+
export declare const PORTFOLIO_SECTION_TYPES: readonly ['portfolioHero', 'portfolioChallenges', 'portfolioBeforeAfter', 'portfolioStrategy', 'portfolioSiteArchitecture', 'portfolioDesignSystem', 'portfolioFeatureSpotlight', 'portfolioGallery', 'portfolioVideo', 'portfolioTechStack', 'portfolioPerformance', 'portfolioSpeedComparison', 'portfolioVerification', 'portfolioResults', 'portfolioMetricsTimeline', 'portfolioConversionFunnel', 'portfolioServices', 'portfolioTeam', 'portfolioTestimonial', 'portfolioDetails', 'portfolioSeo', 'portfolioCTA'];
|
|
54
|
+
export type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];
|
|
55
|
+
export declare function isPortfolioSectionType(value: unknown): value is PortfolioSectionType;
|
|
56
|
+
/**
|
|
57
|
+
* Which stage each section belongs to. Null for the hero (it opens the page,
|
|
58
|
+
* above the story), SEO metadata (never rendered as a section) and the CTA
|
|
59
|
+
* (closes the page, after the story). Typed over every section type so a new
|
|
60
|
+
* type without a stage is a compile error, not a section that renders last.
|
|
61
|
+
*/
|
|
62
|
+
export declare const SECTION_STAGE: Record<PortfolioSectionType, CaseStudyStage | null>;
|
|
63
|
+
/** The stage a section type belongs to, or null when it sits outside the story. Unknown types are null. */
|
|
64
|
+
export declare function sectionStage(type: string): CaseStudyStage | null;
|
|
65
|
+
/**
|
|
66
|
+
* The one section each stage can't do without, and the one an editor offers
|
|
67
|
+
* to add when the stage has nothing. A stage with other sections but not its
|
|
68
|
+
* core (or a stand-in, {@link STAGE_CORE_ALTERNATES}) is told so by
|
|
69
|
+
* {@link caseStudyStageStatus}: a proof stage that's only a speed comparison
|
|
70
|
+
* has no measurement of the finished build.
|
|
71
|
+
*/
|
|
72
|
+
export declare const STAGE_CORE_SECTION: Record<CaseStudyStage, PortfolioSectionType>;
|
|
73
|
+
/**
|
|
74
|
+
* Sections that stand in for a stage's core. A Lighthouse run can only score
|
|
75
|
+
* what a browser can reach without signing in, so an app whose screens sit
|
|
76
|
+
* behind a login has no honest Performance section; its proof is a
|
|
77
|
+
* verification: checks that each say what they cover and what they don't.
|
|
78
|
+
* Either one completes the proof stage. This is deliberately not gated on
|
|
79
|
+
* the kind of project: the status below only sees section types, and the
|
|
80
|
+
* rule for "is this an app" lives with each caller's own data. A verification
|
|
81
|
+
* is a measurement of the finished build with its limits stated, which is
|
|
82
|
+
* what the stage asks for. Single source of truth for what completes a stage.
|
|
83
|
+
*/
|
|
84
|
+
export declare const STAGE_CORE_ALTERNATES: Partial<Record<CaseStudyStage, readonly PortfolioSectionType[]>>;
|
|
85
|
+
/** Every section type that completes a stage: its core first, then its stand-ins. */
|
|
86
|
+
export declare function stageCoreSections(stage: CaseStudyStage): PortfolioSectionType[];
|
|
87
|
+
/**
|
|
88
|
+
* Canonical display order, derived from the list above so it can't disagree
|
|
89
|
+
* with the stages. Renderers sort by this when nobody has reordered; the
|
|
90
|
+
* dashboard's stored order only ever moves a section within its stage.
|
|
91
|
+
*/
|
|
92
|
+
export declare const PORTFOLIO_SECTION_ORDER: Record<PortfolioSectionType, number>;
|
|
93
|
+
/**
|
|
94
|
+
* The least a section needs for the ordering helpers: its type. Anything else
|
|
95
|
+
* rides along. `data` is optional for them and needed by
|
|
96
|
+
* {@link caseStudyStageStatus}, which won't count a stand-in section for a
|
|
97
|
+
* stage's core (a verification) unless its data shows something.
|
|
98
|
+
*/
|
|
99
|
+
export interface SectionLike {
|
|
100
|
+
sectionType: string;
|
|
101
|
+
data?: unknown;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The sections in the order the case study reads.
|
|
105
|
+
*
|
|
106
|
+
* The arc always wins: the hero, then problem, build, proof, outcome, then
|
|
107
|
+
* the CTA. Within a stage, the agency's own order is kept once it has
|
|
108
|
+
* reordered in the dashboard (`sectionsOrdered`); otherwise the canonical
|
|
109
|
+
* order. Before this, a reordered case study rendered in exactly its stored
|
|
110
|
+
* order, so Results could sit above Challenges and the page read backwards.
|
|
111
|
+
* Never mutates.
|
|
112
|
+
*/
|
|
113
|
+
export declare function orderSections<T extends SectionLike>(sections: readonly T[] | null | undefined, options?: {
|
|
114
|
+
sectionsOrdered?: boolean;
|
|
115
|
+
}): T[];
|
|
116
|
+
/** One beat of the story with the sections that tell it. */
|
|
117
|
+
export interface CaseStudyStageGroup<T extends SectionLike = SectionLike> {
|
|
118
|
+
stage: CaseStudyStage;
|
|
119
|
+
/** 1-based position among the stages this case study actually has. */
|
|
120
|
+
index: number;
|
|
121
|
+
sections: T[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The story's beats, in order, each with its sections in reading order
|
|
125
|
+
* ({@link orderSections}). A stage with no sections is left out and the
|
|
126
|
+
* numbering follows, so a software case study with nothing measured runs
|
|
127
|
+
* problem, build, outcome as 1, 2, 3. The hero, SEO and CTA aren't in any
|
|
128
|
+
* group; a renderer places them itself.
|
|
129
|
+
*/
|
|
130
|
+
export declare function groupSectionsByStage<T extends SectionLike>(sections: readonly T[] | null | undefined, options?: {
|
|
131
|
+
sectionsOrdered?: boolean;
|
|
132
|
+
}): CaseStudyStageGroup<T>[];
|
|
133
|
+
/** What each stage of a case study has and what it's missing. */
|
|
134
|
+
export interface CaseStudyStageStatus {
|
|
135
|
+
stage: CaseStudyStage;
|
|
136
|
+
/** The section types present, in reading order. */
|
|
137
|
+
present: PortfolioSectionType[];
|
|
138
|
+
/**
|
|
139
|
+
* True when the stage's core section ({@link STAGE_CORE_SECTION}) is present, or a stand-in for it
|
|
140
|
+
* ({@link STAGE_CORE_ALTERNATES}) is present with something a page can show. Pass each section's `data`:
|
|
141
|
+
* a stand-in without it doesn't count.
|
|
142
|
+
*/
|
|
143
|
+
complete: boolean;
|
|
144
|
+
/** Null when complete; otherwise one sentence on what the stage is missing. */
|
|
145
|
+
missing: string | null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Each stage's status, all four, in order, whether or not the case study has
|
|
149
|
+
* sections in it. The dashboard shows these beside the editor so an empty
|
|
150
|
+
* proof stage is seen before the case study is published, not after.
|
|
151
|
+
*
|
|
152
|
+
* A stage's core section completes it by type. A stand-in for the core (a
|
|
153
|
+
* verification) completes it only when its `data` holds something a page can
|
|
154
|
+
* show, so pass `data`; without it a stand-in reads as empty, never as done.
|
|
155
|
+
*/
|
|
156
|
+
export declare function caseStudyStageStatus(sections: readonly SectionLike[] | null | undefined): CaseStudyStageStatus[];
|
|
157
|
+
export interface CaseStudyNarrative {
|
|
158
|
+
/** One sentence: what this project was, in the end. */
|
|
159
|
+
thesis: string;
|
|
160
|
+
/** One or two sentences opening each stage, specific to this project. A stage may be left unwritten. */
|
|
161
|
+
stages: Partial<Record<CaseStudyStage, string>>;
|
|
162
|
+
}
|
|
163
|
+
/** The longest a thesis or a stage lede may run. Longer is a paragraph, and the sections are the paragraphs. */
|
|
164
|
+
export declare const NARRATIVE_MAX_CHARS = 320;
|
|
165
|
+
/**
|
|
166
|
+
* What's wrong with a narrative, or an empty list when nothing is. An absent
|
|
167
|
+
* narrative is legacy data and passes (renderers fall back to house copy);
|
|
168
|
+
* one that's present must be prose, under the length, with no unknown stage.
|
|
169
|
+
*/
|
|
170
|
+
export declare function narrativeProblems(narrative: unknown): string[];
|
|
171
|
+
/** Agent-native contract: JSON Schema for {@link CaseStudyNarrative}. */
|
|
172
|
+
export declare const CASE_STUDY_NARRATIVE_JSON_SCHEMA: {
|
|
173
|
+
readonly $schema: 'http://json-schema.org/draft-07/schema#';
|
|
174
|
+
readonly $id: 'https://sonor.io/schemas/case-study-narrative.v1.json';
|
|
175
|
+
readonly title: 'CaseStudyNarrative';
|
|
176
|
+
readonly description: 'The thesis of a case study and the lede that opens each of its four stages.';
|
|
177
|
+
readonly type: 'object';
|
|
178
|
+
readonly required: readonly ['thesis'];
|
|
179
|
+
readonly properties: {
|
|
180
|
+
readonly thesis: {
|
|
181
|
+
readonly type: 'string';
|
|
182
|
+
readonly minLength: 1;
|
|
183
|
+
readonly maxLength: 320;
|
|
184
|
+
};
|
|
185
|
+
readonly stages: {
|
|
186
|
+
readonly type: 'object';
|
|
187
|
+
readonly properties: {
|
|
188
|
+
[k: string]: {
|
|
189
|
+
type: string;
|
|
190
|
+
maxLength: number;
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
readonly additionalProperties: false;
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
readonly additionalProperties: false;
|
|
197
|
+
};
|
|
198
|
+
export interface ChallengeLike {
|
|
199
|
+
id?: string | null;
|
|
200
|
+
title?: string;
|
|
201
|
+
}
|
|
202
|
+
export interface PhaseLike {
|
|
203
|
+
resolves?: readonly string[] | null;
|
|
204
|
+
}
|
|
205
|
+
export interface ResultLike {
|
|
206
|
+
proves?: readonly string[] | null;
|
|
207
|
+
}
|
|
208
|
+
/** A stable id for the n-th challenge (1-based): "c1", "c2"… Writers that don't mint their own use this. */
|
|
209
|
+
export declare function challengeId(position: number): string;
|
|
210
|
+
/**
|
|
211
|
+
* The problems with how a case study's story hangs together, each one
|
|
212
|
+
* sentence, or an empty list. Read by the generator's audit and the
|
|
213
|
+
* dashboard. It judges links only where they've been written: a case study
|
|
214
|
+
* whose challenges carry no ids has nothing to check and passes, so legacy
|
|
215
|
+
* content isn't failed for predating the thread.
|
|
216
|
+
*/
|
|
217
|
+
export declare function caseStudyFlowProblems(input: {
|
|
218
|
+
challenges?: readonly ChallengeLike[] | null;
|
|
219
|
+
phases?: readonly PhaseLike[] | null;
|
|
220
|
+
results?: readonly ResultLike[] | null;
|
|
221
|
+
}): string[];
|
|
222
|
+
/**
|
|
223
|
+
* True for a real calendar day written YYYY-MM-DD. "2026-02-31" is not (Date.parse
|
|
224
|
+
* rolls it into March), and neither is a year under 100 (Date.UTC maps those into
|
|
225
|
+
* the 1900s). Single source of truth for what counts as a day.
|
|
226
|
+
*/
|
|
227
|
+
export declare function isRealDay(value: string): boolean;
|
|
228
|
+
/** The most checks one verification lists. It's a short list; the case study isn't an audit report. */
|
|
229
|
+
export declare const VERIFICATION_MAX_CHECKS = 8;
|
|
230
|
+
/** Every field of a check, in the order a renderer reads them. */
|
|
231
|
+
export declare const VERIFICATION_CHECK_FIELDS: readonly ['label', 'result', 'measured', 'where', 'method', 'date', 'scope'];
|
|
232
|
+
export type VerificationCheckField = (typeof VERIFICATION_CHECK_FIELDS)[number];
|
|
233
|
+
/** The longest each text field may run. Each is a line, not a paragraph. `date` is a day, not text. */
|
|
234
|
+
export declare const VERIFICATION_FIELD_MAX_CHARS: {
|
|
235
|
+
readonly label: 80;
|
|
236
|
+
readonly result: 120;
|
|
237
|
+
readonly measured: 200;
|
|
238
|
+
readonly where: 200;
|
|
239
|
+
readonly method: 240;
|
|
240
|
+
readonly scope: 240;
|
|
241
|
+
};
|
|
242
|
+
/** One thing we checked on the finished build. Every field is required. */
|
|
243
|
+
export interface VerificationCheck {
|
|
244
|
+
/** What the check is called: "Sign-in page, mobile speed". */
|
|
245
|
+
label: string;
|
|
246
|
+
/** What came out: "Performance 89", "42 of 42 tables have row-level security". */
|
|
247
|
+
result: string;
|
|
248
|
+
/** What was measured: "Lighthouse performance score of the page a visitor sees before signing in". */
|
|
249
|
+
measured: string;
|
|
250
|
+
/** Where: a URL, or the screen or file. "https://example.com/login", "the migrations in the repository". */
|
|
251
|
+
where: string;
|
|
252
|
+
/** The tool and settings: "Lighthouse 13, mobile preset, median of 3 runs". */
|
|
253
|
+
method: string;
|
|
254
|
+
/** The day it was checked, YYYY-MM-DD. */
|
|
255
|
+
date: string;
|
|
256
|
+
/** What the check does NOT cover, as a limit: "The sign-in page only; the screens behind it weren't measured." */
|
|
257
|
+
scope: string;
|
|
258
|
+
}
|
|
259
|
+
/** The `data` of a `portfolioVerification` section. */
|
|
260
|
+
export interface PortfolioVerificationData {
|
|
261
|
+
checks: VerificationCheck[];
|
|
262
|
+
}
|
|
263
|
+
/** The earliest day a check may carry. An earlier date is a typo, not a check of this build. */
|
|
264
|
+
export declare const VERIFICATION_EARLIEST_DAY = "2000-01-01";
|
|
265
|
+
/**
|
|
266
|
+
* The latest day a check may carry: tomorrow in UTC. A check run today anywhere
|
|
267
|
+
* on earth is dated no later than that (the furthest-ahead zone is UTC+14), so a
|
|
268
|
+
* server's clock can't drop a check dated "today" where the person ran it, and a
|
|
269
|
+
* date further out is a check that hasn't happened. Single source of truth for
|
|
270
|
+
* the allowance: writers and readers both use it, so no one keeps a second one.
|
|
271
|
+
* `now` is for tests and for callers with a clock of their own.
|
|
272
|
+
*/
|
|
273
|
+
export declare function latestVerificationDay(now?: Date): string;
|
|
274
|
+
export interface VerificationOptions {
|
|
275
|
+
/**
|
|
276
|
+
* The latest day a check may carry, YYYY-MM-DD; a later one is a problem. Leave it out to use
|
|
277
|
+
* {@link latestVerificationDay}, which is what a page and a write path both want. Pass one for a fixed clock.
|
|
278
|
+
*/
|
|
279
|
+
latestDay?: string;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* True when a line says nothing: it has no words, or every word is a
|
|
283
|
+
* placeholder or filler ("N/A", "n/a n/a", "TBD", "see above", "none at all").
|
|
284
|
+
*/
|
|
285
|
+
export declare function isPlaceholderText(text: string): boolean;
|
|
286
|
+
/**
|
|
287
|
+
* True when a scope line doesn't state a limit. A scope is "what this check
|
|
288
|
+
* does NOT cover", so it has to be a sentence that uses a word marking where
|
|
289
|
+
* coverage stops ("only", "not", "except", "without", "n't") and says
|
|
290
|
+
* something beyond that word. A coverage claim ("All screens", "Fully
|
|
291
|
+
* tested"), a placeholder ("N/A", "None at all", "Not applicable") and a
|
|
292
|
+
* denied limit ("No restrictions", "Nothing excluded") fail. "Desktop only",
|
|
293
|
+
* "Counted, not exercised" and "No queries ran as a signed-in user" pass.
|
|
294
|
+
*
|
|
295
|
+
* It reads English. It can't judge whether a limit is true or useful, only
|
|
296
|
+
* that one was written; a person still reads the scope.
|
|
297
|
+
*/
|
|
298
|
+
export declare function isVacuousScope(scope: string): boolean;
|
|
299
|
+
/**
|
|
300
|
+
* What's wrong with a verification section's data, or an empty list. The
|
|
301
|
+
* section needs between one and {@link VERIFICATION_MAX_CHECKS} checks, and
|
|
302
|
+
* every field of every check is required, the scope most of all. Whatever
|
|
303
|
+
* stores a verification should run this first. The contract can't make it:
|
|
304
|
+
* until a write path calls it, stored data can hold anything, and
|
|
305
|
+
* {@link readVerificationChecks} is what keeps a bad check off the page.
|
|
306
|
+
*/
|
|
307
|
+
export declare function verificationProblems(data: unknown, options?: VerificationOptions): string[];
|
|
308
|
+
/**
|
|
309
|
+
* The checks a renderer may show: the ones with every field present, within
|
|
310
|
+
* the limits, with a real day and a scope that states a limit, trimmed, in their
|
|
311
|
+
* stored order and no more than {@link VERIFICATION_MAX_CHECKS}. A check that
|
|
312
|
+
* fails is left out, so one with no scope line (or a placeholder for one) never
|
|
313
|
+
* prints. That's all it can promise: whether a scope is true is a person's call.
|
|
314
|
+
* Empty when the data is anything else. Single source of truth for what a case
|
|
315
|
+
* study prints from this section.
|
|
316
|
+
*/
|
|
317
|
+
export declare function readVerificationChecks(data: unknown, options?: VerificationOptions): VerificationCheck[];
|
|
318
|
+
/**
|
|
319
|
+
* The link for a check's `where`: the address when it is exactly one http or
|
|
320
|
+
* https URL with no credentials in it and a public host, else null. A screen
|
|
321
|
+
* name or a file path is printed as text; nothing else is ever made into a link.
|
|
322
|
+
*/
|
|
323
|
+
export declare function verificationLink(where: string): string | null;
|
|
324
|
+
/** Agent-native contract: JSON Schema for {@link PortfolioVerificationData}. What a scope or a where says, and whether a date is a real day in range, are checked by {@link verificationProblems}. */
|
|
325
|
+
export declare const PORTFOLIO_VERIFICATION_JSON_SCHEMA: {
|
|
326
|
+
readonly $schema: 'http://json-schema.org/draft-07/schema#';
|
|
327
|
+
readonly $id: 'https://sonor.io/schemas/portfolio-verification.v1.json';
|
|
328
|
+
readonly title: 'PortfolioVerificationData';
|
|
329
|
+
readonly description: 'A short list of checks on the finished build. Each says what was checked, where, how, when, what came out, and what it does not cover.';
|
|
330
|
+
readonly type: 'object';
|
|
331
|
+
readonly required: readonly ['checks'];
|
|
332
|
+
readonly properties: {
|
|
333
|
+
readonly checks: {
|
|
334
|
+
readonly type: 'array';
|
|
335
|
+
readonly minItems: 1;
|
|
336
|
+
readonly maxItems: 8;
|
|
337
|
+
readonly items: {
|
|
338
|
+
readonly type: 'object';
|
|
339
|
+
readonly required: readonly ["label", "result", "measured", "where", "method", "date", "scope"];
|
|
340
|
+
readonly properties: {
|
|
341
|
+
readonly label: {
|
|
342
|
+
readonly type: 'string';
|
|
343
|
+
readonly minLength: 1;
|
|
344
|
+
readonly maxLength: 80;
|
|
345
|
+
};
|
|
346
|
+
readonly result: {
|
|
347
|
+
readonly type: 'string';
|
|
348
|
+
readonly minLength: 1;
|
|
349
|
+
readonly maxLength: 120;
|
|
350
|
+
};
|
|
351
|
+
readonly measured: {
|
|
352
|
+
readonly type: 'string';
|
|
353
|
+
readonly minLength: 1;
|
|
354
|
+
readonly maxLength: 200;
|
|
355
|
+
};
|
|
356
|
+
readonly where: {
|
|
357
|
+
readonly type: 'string';
|
|
358
|
+
readonly minLength: 1;
|
|
359
|
+
readonly maxLength: 200;
|
|
360
|
+
};
|
|
361
|
+
readonly method: {
|
|
362
|
+
readonly type: 'string';
|
|
363
|
+
readonly minLength: 1;
|
|
364
|
+
readonly maxLength: 240;
|
|
365
|
+
};
|
|
366
|
+
readonly date: {
|
|
367
|
+
readonly type: 'string';
|
|
368
|
+
readonly pattern: '^\\d{4}-\\d{2}-\\d{2}$';
|
|
369
|
+
};
|
|
370
|
+
readonly scope: {
|
|
371
|
+
readonly type: 'string';
|
|
372
|
+
readonly minLength: 1;
|
|
373
|
+
readonly maxLength: 240;
|
|
374
|
+
};
|
|
375
|
+
};
|
|
376
|
+
readonly additionalProperties: false;
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
readonly additionalProperties: false;
|
|
381
|
+
};
|
|
29
382
|
/** Minimal KPI shape the policy reads — callers pass their own richer tiles. */
|
|
30
383
|
export interface KpiLike {
|
|
31
384
|
label: string;
|
|
@@ -9,11 +9,12 @@ export type PublicationPlatform = (typeof PUBLICATION_PLATFORMS)[number];
|
|
|
9
9
|
/** The sections of a profile and of each platform override, in the order they're read. */
|
|
10
10
|
export declare const PUBLICATION_BRANDING_SECTIONS: readonly ['identity', 'colors', 'typography', 'editorial', 'newsletter'];
|
|
11
11
|
export type PublicationBrandingSection = (typeof PUBLICATION_BRANDING_SECTIONS)[number];
|
|
12
|
-
export declare const PUBLICATION_IDENTITY_FIELDS: readonly ['publicationName', 'wordmark', 'tagline', 'publisherName', 'publisherUrl', 'publisherLogoUrl', 'logos'];
|
|
12
|
+
export declare const PUBLICATION_IDENTITY_FIELDS: readonly ['publicationName', 'wordmark', 'tagline', 'publisherName', 'publisherUrl', 'publisherLogoUrl', 'logos', 'squareLogoUrl', 'fullLogoUrl'];
|
|
13
13
|
export declare const PUBLICATION_LOGO_VARIANTS: readonly ['default', 'light', 'dark'];
|
|
14
14
|
export declare const PUBLICATION_COLOR_FIELDS: readonly ['primary', 'secondary', 'accent', 'background', 'surface', 'text', 'muted', 'border'];
|
|
15
15
|
export declare const PUBLICATION_TYPOGRAPHY_FIELDS: readonly ['heading', 'body', 'mono', 'emailHeadingFallback', 'emailBodyFallback'];
|
|
16
|
-
export declare const PUBLICATION_EDITORIAL_FIELDS: readonly ['articleStyle', 'cardLayout', 'charter', 'siteCharters'];
|
|
16
|
+
export declare const PUBLICATION_EDITORIAL_FIELDS: readonly ['articleStyle', 'cardLayout', 'focusClosingBackground', 'charter', 'siteCharters'];
|
|
17
|
+
export declare const FOCUS_CLOSING_BACKGROUNDS: readonly ['planned_photo', 'article_atmosphere'];
|
|
17
18
|
export declare const PUBLICATION_NEWSLETTER_FIELDS: readonly ['name', 'tagline', 'layout', 'maxWidth'];
|
|
18
19
|
/** Fonts every email client has, the only families an email fallback may name. Lowercase. */
|
|
19
20
|
export declare const EMAIL_SAFE_FONTS: ReadonlySet<string>;
|
|
@@ -27,6 +28,11 @@ export interface PublicationIdentity {
|
|
|
27
28
|
publisherUrl?: string;
|
|
28
29
|
/** The publisher's attribution mark, separate from the publication masthead. */
|
|
29
30
|
publisherLogoUrl?: string;
|
|
31
|
+
/** The publication's square mark; independent of theme variants and publisher attribution. */
|
|
32
|
+
squareLogoUrl?: string;
|
|
33
|
+
/** The publication's full/display logo; wordmark remains a text fallback. */
|
|
34
|
+
fullLogoUrl?: string;
|
|
35
|
+
/** Legacy theme variants, not square/full logo roles. */
|
|
30
36
|
logos?: {
|
|
31
37
|
default?: string;
|
|
32
38
|
light?: string;
|
|
@@ -56,6 +62,8 @@ export interface PublicationEditorial {
|
|
|
56
62
|
/** Shared Broadcast style for articles, reviews and generated posts; portfolio is excluded. Legacy wire name. */
|
|
57
63
|
articleStyle?: BroadcastStyleId;
|
|
58
64
|
cardLayout?: PublicationCardLayout;
|
|
65
|
+
/** Focus closing frames may reuse the article's saved atmosphere and real author portrait. */
|
|
66
|
+
focusClosingBackground?: (typeof FOCUS_CLOSING_BACKGROUNDS)[number];
|
|
59
67
|
}
|
|
60
68
|
export interface PublicationNewsletter {
|
|
61
69
|
name?: string;
|
|
@@ -29,6 +29,8 @@ export declare const SITE_CACHE_TAGS: {
|
|
|
29
29
|
readonly portfolio: 'portfolio';
|
|
30
30
|
/** Products, services, events and membership plans (commerce-kit). */
|
|
31
31
|
readonly commerce: 'commerce';
|
|
32
|
+
/** Published managed landing pages. Draft previews never enter this cache. */
|
|
33
|
+
readonly funnels: 'sonor-funnels';
|
|
32
34
|
};
|
|
33
35
|
export type SiteCacheTag = (typeof SITE_CACHE_TAGS)[keyof typeof SITE_CACHE_TAGS];
|
|
34
36
|
/** The body Sonor POSTs to the revalidation route. */
|
package/dist/fleet/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
export { FLEET_HEARTBEAT_PATH, FleetHeartbeat, detectNextVersion, sendFleetHeartbeat } from '../chunk-
|
|
2
|
+
export { FLEET_HEARTBEAT_PATH, FleetHeartbeat, detectNextVersion, sendFleetHeartbeat } from '../chunk-HN7GVGWC.js';
|
|
3
3
|
export { FLEET_CONTRACT_VERSION, FLEET_MODULES, FLEET_MODULES_MAX, FLEET_SITE_MAX_LENGTH, FLEET_VERSION_MAX_LENGTH, FLEET_VERSION_PATTERN, buildFleetHeartbeatPayload, isFleetModule, normalizeFleetModules, normalizeFleetSite, sanitizeFleetVersion } from '../chunk-ZTZQPYP6.js';
|
|
4
4
|
import '../chunk-YC7ELZS3.js';
|
|
5
|
-
import '../chunk-
|
|
6
|
-
import '../chunk-
|
|
7
|
-
import '../chunk-
|
|
8
|
-
import '../chunk-
|
|
5
|
+
import '../chunk-ZQSCMYZO.js';
|
|
6
|
+
import '../chunk-64XODP7M.js';
|
|
7
|
+
import '../chunk-FLOJUMFR.js';
|
|
8
|
+
import '../chunk-7HW4TZB5.js';
|
|
9
9
|
import '../chunk-W7OPEMGJ.js';
|
|
10
|
-
import '../chunk-
|
|
10
|
+
import '../chunk-F6WLHVMQ.js';
|
|
11
11
|
import '../chunk-PKBMQBKP.js';
|