@sonordev/contracts 2.12.0 → 2.13.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 +5 -1
- package/README.md +13 -4
- package/dist/portfolio.d.ts +6 -6
- package/dist/publication-branding.d.ts +4 -1
- package/dist/publication-branding.js +4 -2
- package/dist/publication-branding.mjs +4 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.13.0
|
|
4
|
+
|
|
5
|
+
- Publication branding adds `editorial.focusClosingBackground`, with `planned_photo` and `article_atmosphere` choices. The setting inherits through publication and platform layers, and supports clearing an override. Article atmosphere closing frames reuse saved artwork and the real author portrait; other publications retain their existing behavior.
|
|
6
|
+
|
|
3
7
|
## 2.12.0
|
|
4
8
|
|
|
5
9
|
A minor release: `@sonordev/contracts/portfolio` now holds the structure of a case study's story. Every other entry is unchanged, and nothing already exported from `portfolio` changed.
|
|
@@ -7,7 +11,7 @@ A minor release: `@sonordev/contracts/portfolio` now holds the structure of a ca
|
|
|
7
11
|
- **The case study arc.** `CASE_STUDY_STAGES` names the four beats every case study reads in (`problem`, `build`, `proof`, `outcome`), `CASE_STUDY_STAGE_COPY` gives each a generic name and the question it answers, `PORTFOLIO_SECTION_TYPES` is every section type in canonical reading order, and `SECTION_STAGE` / `sectionStage` say which stage each belongs to. The section list used to be kept separately by the Sonor API and agency-site-kit; this is now the one copy.
|
|
8
12
|
- **Reading order.** `orderSections` always keeps the arc (hero, problem, build, proof, outcome, CTA) and keeps an agency's stored order only within a stage, so a reordered case study can no longer put its results above its challenges. `groupSectionsByStage` returns the beats a case study actually has, numbered in order. `PORTFOLIO_SECTION_ORDER` is derived from the list. Performance now reads before Results: the proof comes before the outcome.
|
|
9
13
|
- **Stage status.** `caseStudyStageStatus` reports all four stages, complete only with the stage's core section (`STAGE_CORE_SECTION`), with one sentence on what's missing.
|
|
10
|
-
- **Stage names.**
|
|
14
|
+
- **Stage names.** A site can call the four stages what it likes ("Heat", "Shape", "Temper", "Finish"); `CaseStudyStageNames` is that map, `stageName(names, stage)` reads it with the generic name as the fallback, `stageNamesProblems` validates it and `STAGE_NAME_MAX_CHARS` is the length limit. The ids never change; only what people see does.
|
|
11
15
|
- **The narrative.** `CaseStudyNarrative` is a case study's thesis and one lede per stage, written for the project rather than borrowed from the house. `narrativeProblems` validates one, `NARRATIVE_MAX_CHARS` is the length limit and `CASE_STUDY_NARRATIVE_JSON_SCHEMA` the same rule as a schema. An absent narrative passes.
|
|
12
16
|
- **The thread.** A challenge carries an `id` (`challengeId(n)`), a phase `resolves` challenges and a result `proves` them; `caseStudyFlowProblems` names every challenge nothing resolves or proves and every dangling reference, and passes content written before the thread existed.
|
|
13
17
|
|
package/README.md
CHANGED
|
@@ -39,6 +39,15 @@ pnpm add @sonordev/contracts
|
|
|
39
39
|
| `@sonordev/contracts/publication-branding` | What a publication's branding may hold and what a change may set (`createPublicationBrandingValidator`), how a change merges, the editorial charter, and safe font stacks | the Sonor API stores and resolves it, Signal checks an agent's change before filing it, the dashboard edits it |
|
|
40
40
|
| `@sonordev/contracts/public-url-host` | Which literal hosts are refused as public fetch destinations (`isBlockedPublicUrlHost`) | the Sonor API and Signal's URL validators and network fetchers |
|
|
41
41
|
|
|
42
|
+
## Focus closing artwork
|
|
43
|
+
|
|
44
|
+
`publication-branding` accepts `editorial.focusClosingBackground` at publication
|
|
45
|
+
and platform levels. `planned_photo` keeps the planned closing image;
|
|
46
|
+
`article_atmosphere` reuses the article's saved atmosphere with its real author
|
|
47
|
+
portrait. Omitting the field keeps existing behavior. A patch value of `null`
|
|
48
|
+
clears the override and restores inheritance. The article-atmosphere mode requires
|
|
49
|
+
both saved sources; it doesn't invent a portrait or substitute unrelated artwork.
|
|
50
|
+
|
|
42
51
|
## Public URL hosts
|
|
43
52
|
|
|
44
53
|
`isBlockedPublicUrlHost(host)` shares the APIs' loopback, private IPv4,
|
|
@@ -829,10 +838,10 @@ the dashboard that edits it and every site that renders it.
|
|
|
829
838
|
only when the stage's core section (`STAGE_CORE_SECTION`: challenges,
|
|
830
839
|
strategy, performance, results) is present, with one sentence on what's
|
|
831
840
|
missing. The dashboard shows it beside the editor.
|
|
832
|
-
- **Stage names.** The ids are fixed; what people see isn't.
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
public portfolio config and the
|
|
841
|
+
- **Stage names.** The ids are fixed; what people see isn't. A site names
|
|
842
|
+
the four stages in the dashboard ("Heat", "Shape", "Temper", "Finish"),
|
|
843
|
+
per project like the rest of its branding, and `CaseStudyStageNames`
|
|
844
|
+
carries that map to the editor, the public portfolio config and the site. `stageName(names, stage)`
|
|
836
845
|
reads it with the generic name as the fallback; `stageNamesProblems`
|
|
837
846
|
validates it and `STAGE_NAME_MAX_CHARS` caps each name.
|
|
838
847
|
- **`CaseStudyNarrative`** is what the story says above its sections: one
|
package/dist/portfolio.d.ts
CHANGED
|
@@ -35,11 +35,11 @@ export declare const CASE_STUDY_STAGE_COPY: Record<CaseStudyStage, {
|
|
|
35
35
|
question: string;
|
|
36
36
|
}>;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
* "Finish"), set
|
|
40
|
-
* the editor, the public portfolio config,
|
|
41
|
-
* unnamed falls back to its generic name. The
|
|
42
|
-
* people see does.
|
|
38
|
+
* A site's own names for the four stages ("Heat", "Shape", "Temper",
|
|
39
|
+
* "Finish"), set per project in the dashboard (branding is per project) and
|
|
40
|
+
* read everywhere a stage is named: the editor, the public portfolio config,
|
|
41
|
+
* the site itself. A stage left unnamed falls back to its generic name. The
|
|
42
|
+
* ids never change; only what people see does.
|
|
43
43
|
*/
|
|
44
44
|
export type CaseStudyStageNames = Partial<Record<CaseStudyStage, string>>;
|
|
45
45
|
/** The longest a stage name may run. It's a heading word, a rail label, a tab. */
|
|
@@ -49,7 +49,7 @@ export declare const STAGE_NAME_MAX_CHARS = 24;
|
|
|
49
49
|
* (generic names); present must be an object of short text keyed by stage.
|
|
50
50
|
*/
|
|
51
51
|
export declare function stageNamesProblems(names: unknown): string[];
|
|
52
|
-
/** The name a stage is shown under: the
|
|
52
|
+
/** The name a stage is shown under: the site's own when set, else the generic one. */
|
|
53
53
|
export declare function stageName(names: CaseStudyStageNames | null | undefined, stage: CaseStudyStage): string;
|
|
54
54
|
/**
|
|
55
55
|
* Every section type a case study can hold, in canonical reading order:
|
|
@@ -13,7 +13,8 @@ export declare const PUBLICATION_IDENTITY_FIELDS: readonly ['publicationName', '
|
|
|
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>;
|
|
@@ -56,6 +57,8 @@ export interface PublicationEditorial {
|
|
|
56
57
|
/** Shared Broadcast style for articles, reviews and generated posts; portfolio is excluded. Legacy wire name. */
|
|
57
58
|
articleStyle?: BroadcastStyleId;
|
|
58
59
|
cardLayout?: PublicationCardLayout;
|
|
60
|
+
/** Focus closing frames may reuse the article's saved atmosphere and real author portrait. */
|
|
61
|
+
focusClosingBackground?: (typeof FOCUS_CLOSING_BACKGROUNDS)[number];
|
|
59
62
|
}
|
|
60
63
|
export interface PublicationNewsletter {
|
|
61
64
|
name?: string;
|
|
@@ -138,7 +138,8 @@ var PUBLICATION_IDENTITY_FIELDS = ["publicationName", "wordmark", "tagline", "pu
|
|
|
138
138
|
var PUBLICATION_LOGO_VARIANTS = ["default", "light", "dark"];
|
|
139
139
|
var PUBLICATION_COLOR_FIELDS = ["primary", "secondary", "accent", "background", "surface", "text", "muted", "border"];
|
|
140
140
|
var PUBLICATION_TYPOGRAPHY_FIELDS = ["heading", "body", "mono", "emailHeadingFallback", "emailBodyFallback"];
|
|
141
|
-
var PUBLICATION_EDITORIAL_FIELDS = ["articleStyle", "cardLayout", "charter", "siteCharters"];
|
|
141
|
+
var PUBLICATION_EDITORIAL_FIELDS = ["articleStyle", "cardLayout", "focusClosingBackground", "charter", "siteCharters"];
|
|
142
|
+
var FOCUS_CLOSING_BACKGROUNDS = ["planned_photo", "article_atmosphere"];
|
|
142
143
|
var PUBLICATION_NEWSLETTER_FIELDS = ["name", "tagline", "layout", "maxWidth"];
|
|
143
144
|
var EMAIL_SAFE_FONTS = /* @__PURE__ */ new Set([
|
|
144
145
|
"arial",
|
|
@@ -279,7 +280,7 @@ function createPublicationBrandingValidator(options) {
|
|
|
279
280
|
throw new PublicationBrandingValidationError(`${prefix}.${key}`, error.message);
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
|
-
const allowed = key === "articleStyle" ? BROADCAST_STYLES : patch ? PUBLICATION_CARD_LAYOUTS : SAVED_PUBLICATION_CARD_LAYOUTS;
|
|
283
|
+
const allowed = key === "articleStyle" ? BROADCAST_STYLES : key === "focusClosingBackground" ? FOCUS_CLOSING_BACKGROUNDS : patch ? PUBLICATION_CARD_LAYOUTS : SAVED_PUBLICATION_CARD_LAYOUTS;
|
|
283
284
|
if (typeof v !== "string" || !allowed.includes(v)) throw new PublicationBrandingValidationError(`${prefix}.${key}`, `choose ${allowed.join(", ")}`);
|
|
284
285
|
return v;
|
|
285
286
|
});
|
|
@@ -328,6 +329,7 @@ function createPublicationBrandingValidator(options) {
|
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
exports.EMAIL_SAFE_FONTS = EMAIL_SAFE_FONTS;
|
|
332
|
+
exports.FOCUS_CLOSING_BACKGROUNDS = FOCUS_CLOSING_BACKGROUNDS;
|
|
331
333
|
exports.GENERIC_FONT_FAMILIES = GENERIC_FONT_FAMILIES;
|
|
332
334
|
exports.PUBLICATION_BRANDING_SECTIONS = PUBLICATION_BRANDING_SECTIONS;
|
|
333
335
|
exports.PUBLICATION_BRANDING_VERSION = PUBLICATION_BRANDING_VERSION;
|
|
@@ -136,7 +136,8 @@ var PUBLICATION_IDENTITY_FIELDS = ["publicationName", "wordmark", "tagline", "pu
|
|
|
136
136
|
var PUBLICATION_LOGO_VARIANTS = ["default", "light", "dark"];
|
|
137
137
|
var PUBLICATION_COLOR_FIELDS = ["primary", "secondary", "accent", "background", "surface", "text", "muted", "border"];
|
|
138
138
|
var PUBLICATION_TYPOGRAPHY_FIELDS = ["heading", "body", "mono", "emailHeadingFallback", "emailBodyFallback"];
|
|
139
|
-
var PUBLICATION_EDITORIAL_FIELDS = ["articleStyle", "cardLayout", "charter", "siteCharters"];
|
|
139
|
+
var PUBLICATION_EDITORIAL_FIELDS = ["articleStyle", "cardLayout", "focusClosingBackground", "charter", "siteCharters"];
|
|
140
|
+
var FOCUS_CLOSING_BACKGROUNDS = ["planned_photo", "article_atmosphere"];
|
|
140
141
|
var PUBLICATION_NEWSLETTER_FIELDS = ["name", "tagline", "layout", "maxWidth"];
|
|
141
142
|
var EMAIL_SAFE_FONTS = /* @__PURE__ */ new Set([
|
|
142
143
|
"arial",
|
|
@@ -277,7 +278,7 @@ function createPublicationBrandingValidator(options) {
|
|
|
277
278
|
throw new PublicationBrandingValidationError(`${prefix}.${key}`, error.message);
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
|
-
const allowed = key === "articleStyle" ? BROADCAST_STYLES : patch ? PUBLICATION_CARD_LAYOUTS : SAVED_PUBLICATION_CARD_LAYOUTS;
|
|
281
|
+
const allowed = key === "articleStyle" ? BROADCAST_STYLES : key === "focusClosingBackground" ? FOCUS_CLOSING_BACKGROUNDS : patch ? PUBLICATION_CARD_LAYOUTS : SAVED_PUBLICATION_CARD_LAYOUTS;
|
|
281
282
|
if (typeof v !== "string" || !allowed.includes(v)) throw new PublicationBrandingValidationError(`${prefix}.${key}`, `choose ${allowed.join(", ")}`);
|
|
282
283
|
return v;
|
|
283
284
|
});
|
|
@@ -325,4 +326,4 @@ function createPublicationBrandingValidator(options) {
|
|
|
325
326
|
};
|
|
326
327
|
}
|
|
327
328
|
|
|
328
|
-
export { EMAIL_SAFE_FONTS, GENERIC_FONT_FAMILIES, PUBLICATION_BRANDING_SECTIONS, PUBLICATION_BRANDING_VERSION, PUBLICATION_CHARTER_FIELDS, PUBLICATION_COLOR_FIELDS, PUBLICATION_EDITORIAL_FIELDS, PUBLICATION_IDENTITY_FIELDS, PUBLICATION_LOGO_VARIANTS, PUBLICATION_NEWSLETTER_FIELDS, PUBLICATION_PLATFORMS, PUBLICATION_TYPOGRAPHY_FIELDS, PublicationBrandingValidationError, containsControlCharacters, createPublicationBrandingValidator, mergePublicationBrandingLayers, parsePublicationCharter, parseSiteCharters, publicationCharterContext, publicationCharterFor, safeFontStack };
|
|
329
|
+
export { EMAIL_SAFE_FONTS, FOCUS_CLOSING_BACKGROUNDS, GENERIC_FONT_FAMILIES, PUBLICATION_BRANDING_SECTIONS, PUBLICATION_BRANDING_VERSION, PUBLICATION_CHARTER_FIELDS, PUBLICATION_COLOR_FIELDS, PUBLICATION_EDITORIAL_FIELDS, PUBLICATION_IDENTITY_FIELDS, PUBLICATION_LOGO_VARIANTS, PUBLICATION_NEWSLETTER_FIELDS, PUBLICATION_PLATFORMS, PUBLICATION_TYPOGRAPHY_FIELDS, PublicationBrandingValidationError, containsControlCharacters, createPublicationBrandingValidator, mergePublicationBrandingLayers, parsePublicationCharter, parseSiteCharters, publicationCharterContext, publicationCharterFor, safeFontStack };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sonordev/contracts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "The rules Sonor's sites, APIs and dashboard share: popup blocks and site design tokens, site hosts, SEO page identity, title quality, llms.txt sanitizers, the forms honeypot, fleet heartbeats, slot content and signing, site cache tags, portfolio metrics, the house voice's contraction rule, how a proposal's site plan counts its pages, what separates an error page from a page's content, which stored JSON-LD is a template's unfilled placeholder, where prose splits into sentences, which Sonor records Signal reads into a project's knowledge and the address each one is stored under, what outbound email and text may claim about a business and whether a sent email arrived intact, what a segment of outreach is and who may turn one on, a page's own name taken from its title or its slug, which Broadcast card layouts and review compositions are drawn and what a retired one draws as, what a publication's branding may hold, and which literal hosts are refused as public fetch destinations. Pure functions, no dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|