@weatherboard/cms 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,6 +50,57 @@ const resolved = parseSiteHero({
50
50
  }, fallback);
51
51
  ```
52
52
 
53
+ ## Page sections (0.3.0)
54
+
55
+ `parseContentPage` (and `contentPageQuery`, `contentPagesQuery`) read the
56
+ optional `page.sections[]` that `@weatherboard/sanity-schema-base` declares
57
+ (A-1889). Each entry maps to a `kind`, listed in `READER_SECTION_KINDS`:
58
+
59
+ | Sanity `_type` | `kind` | composition | default layout |
60
+ | --- | --- | --- | --- |
61
+ | `featureStoriesBlock`, `surfaceCardBlock`, `accentBandBlock`, `locationsBlock` | `feature_stories`, `cta`, `accent_band`, `locations` | none | none |
62
+ | `textMediaBlock` | `text_media` | Split | `media-end` |
63
+ | `faqBlock` (items `faqItem`) | `faq` | HeadingList | `beside` |
64
+ | `galleryBlock` | `gallery` | StaggerRow | `staggered` |
65
+ | `contactBlock` | `contact` | Split | `media-end` |
66
+
67
+ The four home kinds read exactly as they do on the home page. The new kinds
68
+ return every key, `null` where there is nothing, with images as
69
+ `SectionImageInput` (`asset` id, `alt`, `caption`, `hotspot` `{ x, y }`).
70
+
71
+ The layout values are the A-1876 variant names, exported as `SPLIT_LAYOUTS`
72
+ (`media-end`, `media-start`, `media-below`), `HEADING_LIST_LAYOUTS` (`beside`,
73
+ `stacked`) and `STAGGER_ROW_LAYOUTS` (`staggered`, `aligned`), with the defaults
74
+ in `SECTION_LAYOUT_DEFAULTS`. A later contract test can compare them with the UI
75
+ package's lists.
76
+
77
+ `PageContent.sections` and `PageContent.warnings` are present only when the
78
+ stored page has a `sections` list, so a page without one reads exactly as it did
79
+ in 0.2.0. Each warning is `{ code, where }`, a code from `SECTION_WARNINGS` and
80
+ the path in the stored document, never copy:
81
+
82
+ - `section.layout.defaulted`: missing or unknown `layout`; the composition
83
+ default renders. It never throws and never yields an empty section.
84
+ - `section.type.unknown`: a section `_type` this reader does not know is
85
+ skipped; the rest of the page reads.
86
+ - `section.image.missing-alt`: an image without alt text is dropped, never
87
+ returned without it. `text_media.image` is then `null`; a gallery keeps its
88
+ other photos.
89
+ - `section.image.missing-asset`: an image with no uploaded asset is dropped.
90
+ - `section.empty.skipped`: a gallery whose every photo was dropped, or a contact
91
+ section with email, phone, text and button all off, is skipped.
92
+
93
+ Everything else stays a named refusal, as for the rest of the reader: missing
94
+ required copy, a FAQ with no questions, a gallery outside 2 to 4 photos, a
95
+ repeated jump link or a button jumping to a section that is not on the page
96
+ throws `MalformedContentError` naming the path. Page fallbacks that carry
97
+ `sections` are validated against the same kinds.
98
+
99
+ The warnings travel in the page data, not in the server reader's
100
+ `CmsResolution.warnings`, which still names only the hero warning.
101
+
102
+ 0.3.0 is an additive minor under the pilot semver policy: a new reader kind.
103
+
53
104
  ## Schema and release delivery
54
105
 
55
106
  `@weatherboard/sanity-schema-base` derives the hero schema from these rules:
@@ -0,0 +1,136 @@
1
+ /**
2
+ * A-1915 (A-1739 M1c). The reader's half of the shared section set that
3
+ * `@weatherboard/sanity-schema-base` declares in `src/sections.ts` (A-1889):
4
+ * `page.sections[]`, drawn from the four home section types plus
5
+ * `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock` and
6
+ * `contactBlock`.
7
+ *
8
+ * ===========================================================================
9
+ * LAYOUTS ARE THE A-1876 VARIANT NAMES, EXACTLY
10
+ * ===========================================================================
11
+ *
12
+ * The three lists below are the composition variant names, spelled as A-1876
13
+ * spells them, so a contract test can import them and compare them with the
14
+ * UI package's. A missing or unknown stored `layout` resolves to the
15
+ * composition's default and records `section.layout.defaulted`, the rule cms
16
+ * 0.2.0 set for `siteHero.layout`. It never throws and never yields an empty
17
+ * section.
18
+ *
19
+ * ===========================================================================
20
+ * WARNINGS, NOT REFUSALS, FOR THREE THINGS
21
+ * ===========================================================================
22
+ *
23
+ * The rest of this reader refuses a malformed document by name. Three things
24
+ * in a section are warnings instead, each recorded with its path:
25
+ *
26
+ * section.layout.defaulted missing or unknown layout: the default renders
27
+ * section.type.unknown a section `_type` this reader does not know is
28
+ * skipped, so a newer model never breaks a page
29
+ * section.image.missing-alt an image without alt text is DROPPED, never
30
+ * rendered without it
31
+ * section.image.missing-asset an image with no uploaded asset is dropped
32
+ * section.empty.skipped a section left with nothing to show (a gallery
33
+ * whose every photo was dropped, a contact
34
+ * section switched off and empty) is skipped
35
+ */
36
+ import type { ActionInput, CompositionBlockInput, CopyInput } from "./site-content.cjs";
37
+ /** `Split`: text with media. */
38
+ export declare const SPLIT_LAYOUTS: readonly ["media-end", "media-start", "media-below"];
39
+ /** `HeadingList`: a heading column and a list column. */
40
+ export declare const HEADING_LIST_LAYOUTS: readonly ["beside", "stacked"];
41
+ /** `StaggerRow`: a row of two to four items. */
42
+ export declare const STAGGER_ROW_LAYOUTS: readonly ["staggered", "aligned"];
43
+ export type SplitLayout = (typeof SPLIT_LAYOUTS)[number];
44
+ export type HeadingListLayout = (typeof HEADING_LIST_LAYOUTS)[number];
45
+ export type StaggerRowLayout = (typeof STAGGER_ROW_LAYOUTS)[number];
46
+ /** What a missing or unknown `layout` resolves to, per composition. */
47
+ export declare const SECTION_LAYOUT_DEFAULTS: {
48
+ readonly Split: "media-end";
49
+ readonly HeadingList: "beside";
50
+ readonly StaggerRow: "staggered";
51
+ };
52
+ /** Sanity `_type` on the left, the reader's `kind` on the right; the same rows as schema-base's `SECTION_CONTRACTS`. */
53
+ export declare const READER_SECTION_KINDS: {
54
+ readonly featureStoriesBlock: "feature_stories";
55
+ readonly surfaceCardBlock: "cta";
56
+ readonly accentBandBlock: "accent_band";
57
+ readonly locationsBlock: "locations";
58
+ readonly textMediaBlock: "text_media";
59
+ readonly faqBlock: "faq";
60
+ readonly galleryBlock: "gallery";
61
+ readonly contactBlock: "contact";
62
+ };
63
+ /** A gallery is a `StaggerRow`, which holds two to four items. */
64
+ export declare const READER_GALLERY_PHOTO_MIN = 2;
65
+ export declare const READER_GALLERY_PHOTO_LIMIT = 4;
66
+ export declare const SECTION_WARNINGS: {
67
+ readonly layoutDefaulted: "section.layout.defaulted";
68
+ readonly unknownType: "section.type.unknown";
69
+ readonly imageMissingAlt: "section.image.missing-alt";
70
+ readonly imageMissingAsset: "section.image.missing-asset";
71
+ readonly emptySkipped: "section.empty.skipped";
72
+ };
73
+ export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
74
+ /** A named warning and the path in the stored document it is about. Never copy. */
75
+ export type SectionWarning = {
76
+ readonly code: SectionWarningCode;
77
+ readonly where: string;
78
+ };
79
+ /** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
80
+ export type SectionImageInput = {
81
+ readonly asset: string;
82
+ readonly alt: string;
83
+ readonly caption: string | null;
84
+ readonly hotspot: {
85
+ readonly x: number;
86
+ readonly y: number;
87
+ } | null;
88
+ };
89
+ export type TextMediaSectionInput = {
90
+ readonly kind: "text_media";
91
+ readonly anchor: string | null;
92
+ readonly layout: SplitLayout;
93
+ readonly heading: CopyInput;
94
+ readonly tail: CopyInput | null;
95
+ readonly body: CopyInput;
96
+ readonly action: ActionInput | null;
97
+ /** `null` only when the stored photo was dropped (no alt text or no asset); a warning names it. */
98
+ readonly image: SectionImageInput | null;
99
+ };
100
+ export type FaqSectionInput = {
101
+ readonly kind: "faq";
102
+ readonly anchor: string | null;
103
+ readonly layout: HeadingListLayout;
104
+ readonly heading: CopyInput;
105
+ readonly tail: CopyInput | null;
106
+ readonly intro: CopyInput | null;
107
+ readonly items: readonly {
108
+ readonly question: CopyInput;
109
+ readonly answer: CopyInput;
110
+ }[];
111
+ };
112
+ export type GallerySectionInput = {
113
+ readonly kind: "gallery";
114
+ readonly anchor: string | null;
115
+ readonly layout: StaggerRowLayout;
116
+ readonly heading: CopyInput | null;
117
+ readonly images: readonly SectionImageInput[];
118
+ };
119
+ export type ContactSectionInput = {
120
+ readonly kind: "contact";
121
+ readonly anchor: string | null;
122
+ readonly layout: SplitLayout;
123
+ readonly heading: CopyInput;
124
+ readonly body: CopyInput | null;
125
+ /** Show the site settings' email address. */
126
+ readonly showEmail: boolean;
127
+ /** Show the site settings' phone number. */
128
+ readonly showPhone: boolean;
129
+ readonly action: ActionInput | null;
130
+ readonly image: SectionImageInput | null;
131
+ };
132
+ export type NewSectionInput = TextMediaSectionInput | FaqSectionInput | GallerySectionInput | ContactSectionInput;
133
+ /** One entry of `PageContent.sections`: a home section kind or one of the new kinds. */
134
+ export type PageSectionInput = Exclude<CompositionBlockInput, {
135
+ readonly kind: "campaign";
136
+ }> | NewSectionInput;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * A-1915 (A-1739 M1c). The reader's half of the shared section set that
3
+ * `@weatherboard/sanity-schema-base` declares in `src/sections.ts` (A-1889):
4
+ * `page.sections[]`, drawn from the four home section types plus
5
+ * `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock` and
6
+ * `contactBlock`.
7
+ *
8
+ * ===========================================================================
9
+ * LAYOUTS ARE THE A-1876 VARIANT NAMES, EXACTLY
10
+ * ===========================================================================
11
+ *
12
+ * The three lists below are the composition variant names, spelled as A-1876
13
+ * spells them, so a contract test can import them and compare them with the
14
+ * UI package's. A missing or unknown stored `layout` resolves to the
15
+ * composition's default and records `section.layout.defaulted`, the rule cms
16
+ * 0.2.0 set for `siteHero.layout`. It never throws and never yields an empty
17
+ * section.
18
+ *
19
+ * ===========================================================================
20
+ * WARNINGS, NOT REFUSALS, FOR THREE THINGS
21
+ * ===========================================================================
22
+ *
23
+ * The rest of this reader refuses a malformed document by name. Three things
24
+ * in a section are warnings instead, each recorded with its path:
25
+ *
26
+ * section.layout.defaulted missing or unknown layout: the default renders
27
+ * section.type.unknown a section `_type` this reader does not know is
28
+ * skipped, so a newer model never breaks a page
29
+ * section.image.missing-alt an image without alt text is DROPPED, never
30
+ * rendered without it
31
+ * section.image.missing-asset an image with no uploaded asset is dropped
32
+ * section.empty.skipped a section left with nothing to show (a gallery
33
+ * whose every photo was dropped, a contact
34
+ * section switched off and empty) is skipped
35
+ */
36
+ import type { ActionInput, CompositionBlockInput, CopyInput } from "./site-content.mjs";
37
+ /** `Split`: text with media. */
38
+ export declare const SPLIT_LAYOUTS: readonly ["media-end", "media-start", "media-below"];
39
+ /** `HeadingList`: a heading column and a list column. */
40
+ export declare const HEADING_LIST_LAYOUTS: readonly ["beside", "stacked"];
41
+ /** `StaggerRow`: a row of two to four items. */
42
+ export declare const STAGGER_ROW_LAYOUTS: readonly ["staggered", "aligned"];
43
+ export type SplitLayout = (typeof SPLIT_LAYOUTS)[number];
44
+ export type HeadingListLayout = (typeof HEADING_LIST_LAYOUTS)[number];
45
+ export type StaggerRowLayout = (typeof STAGGER_ROW_LAYOUTS)[number];
46
+ /** What a missing or unknown `layout` resolves to, per composition. */
47
+ export declare const SECTION_LAYOUT_DEFAULTS: {
48
+ readonly Split: "media-end";
49
+ readonly HeadingList: "beside";
50
+ readonly StaggerRow: "staggered";
51
+ };
52
+ /** Sanity `_type` on the left, the reader's `kind` on the right; the same rows as schema-base's `SECTION_CONTRACTS`. */
53
+ export declare const READER_SECTION_KINDS: {
54
+ readonly featureStoriesBlock: "feature_stories";
55
+ readonly surfaceCardBlock: "cta";
56
+ readonly accentBandBlock: "accent_band";
57
+ readonly locationsBlock: "locations";
58
+ readonly textMediaBlock: "text_media";
59
+ readonly faqBlock: "faq";
60
+ readonly galleryBlock: "gallery";
61
+ readonly contactBlock: "contact";
62
+ };
63
+ /** A gallery is a `StaggerRow`, which holds two to four items. */
64
+ export declare const READER_GALLERY_PHOTO_MIN = 2;
65
+ export declare const READER_GALLERY_PHOTO_LIMIT = 4;
66
+ export declare const SECTION_WARNINGS: {
67
+ readonly layoutDefaulted: "section.layout.defaulted";
68
+ readonly unknownType: "section.type.unknown";
69
+ readonly imageMissingAlt: "section.image.missing-alt";
70
+ readonly imageMissingAsset: "section.image.missing-asset";
71
+ readonly emptySkipped: "section.empty.skipped";
72
+ };
73
+ export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
74
+ /** A named warning and the path in the stored document it is about. Never copy. */
75
+ export type SectionWarning = {
76
+ readonly code: SectionWarningCode;
77
+ readonly where: string;
78
+ };
79
+ /** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
80
+ export type SectionImageInput = {
81
+ readonly asset: string;
82
+ readonly alt: string;
83
+ readonly caption: string | null;
84
+ readonly hotspot: {
85
+ readonly x: number;
86
+ readonly y: number;
87
+ } | null;
88
+ };
89
+ export type TextMediaSectionInput = {
90
+ readonly kind: "text_media";
91
+ readonly anchor: string | null;
92
+ readonly layout: SplitLayout;
93
+ readonly heading: CopyInput;
94
+ readonly tail: CopyInput | null;
95
+ readonly body: CopyInput;
96
+ readonly action: ActionInput | null;
97
+ /** `null` only when the stored photo was dropped (no alt text or no asset); a warning names it. */
98
+ readonly image: SectionImageInput | null;
99
+ };
100
+ export type FaqSectionInput = {
101
+ readonly kind: "faq";
102
+ readonly anchor: string | null;
103
+ readonly layout: HeadingListLayout;
104
+ readonly heading: CopyInput;
105
+ readonly tail: CopyInput | null;
106
+ readonly intro: CopyInput | null;
107
+ readonly items: readonly {
108
+ readonly question: CopyInput;
109
+ readonly answer: CopyInput;
110
+ }[];
111
+ };
112
+ export type GallerySectionInput = {
113
+ readonly kind: "gallery";
114
+ readonly anchor: string | null;
115
+ readonly layout: StaggerRowLayout;
116
+ readonly heading: CopyInput | null;
117
+ readonly images: readonly SectionImageInput[];
118
+ };
119
+ export type ContactSectionInput = {
120
+ readonly kind: "contact";
121
+ readonly anchor: string | null;
122
+ readonly layout: SplitLayout;
123
+ readonly heading: CopyInput;
124
+ readonly body: CopyInput | null;
125
+ /** Show the site settings' email address. */
126
+ readonly showEmail: boolean;
127
+ /** Show the site settings' phone number. */
128
+ readonly showPhone: boolean;
129
+ readonly action: ActionInput | null;
130
+ readonly image: SectionImageInput | null;
131
+ };
132
+ export type NewSectionInput = TextMediaSectionInput | FaqSectionInput | GallerySectionInput | ContactSectionInput;
133
+ /** One entry of `PageContent.sections`: a home section kind or one of the new kinds. */
134
+ export type PageSectionInput = Exclude<CompositionBlockInput, {
135
+ readonly kind: "campaign";
136
+ }> | NewSectionInput;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * A-1915 (A-1739 M1c). The reader's half of the shared section set that
3
+ * `@weatherboard/sanity-schema-base` declares in `src/sections.ts` (A-1889):
4
+ * `page.sections[]`, drawn from the four home section types plus
5
+ * `textMediaBlock`, `faqBlock` (items `faqItem`), `galleryBlock` and
6
+ * `contactBlock`.
7
+ *
8
+ * ===========================================================================
9
+ * LAYOUTS ARE THE A-1876 VARIANT NAMES, EXACTLY
10
+ * ===========================================================================
11
+ *
12
+ * The three lists below are the composition variant names, spelled as A-1876
13
+ * spells them, so a contract test can import them and compare them with the
14
+ * UI package's. A missing or unknown stored `layout` resolves to the
15
+ * composition's default and records `section.layout.defaulted`, the rule cms
16
+ * 0.2.0 set for `siteHero.layout`. It never throws and never yields an empty
17
+ * section.
18
+ *
19
+ * ===========================================================================
20
+ * WARNINGS, NOT REFUSALS, FOR THREE THINGS
21
+ * ===========================================================================
22
+ *
23
+ * The rest of this reader refuses a malformed document by name. Three things
24
+ * in a section are warnings instead, each recorded with its path:
25
+ *
26
+ * section.layout.defaulted missing or unknown layout: the default renders
27
+ * section.type.unknown a section `_type` this reader does not know is
28
+ * skipped, so a newer model never breaks a page
29
+ * section.image.missing-alt an image without alt text is DROPPED, never
30
+ * rendered without it
31
+ * section.image.missing-asset an image with no uploaded asset is dropped
32
+ * section.empty.skipped a section left with nothing to show (a gallery
33
+ * whose every photo was dropped, a contact
34
+ * section switched off and empty) is skipped
35
+ */
36
+ import type { ActionInput, CompositionBlockInput, CopyInput } from "./site-content.js";
37
+ /** `Split`: text with media. */
38
+ export declare const SPLIT_LAYOUTS: readonly ["media-end", "media-start", "media-below"];
39
+ /** `HeadingList`: a heading column and a list column. */
40
+ export declare const HEADING_LIST_LAYOUTS: readonly ["beside", "stacked"];
41
+ /** `StaggerRow`: a row of two to four items. */
42
+ export declare const STAGGER_ROW_LAYOUTS: readonly ["staggered", "aligned"];
43
+ export type SplitLayout = (typeof SPLIT_LAYOUTS)[number];
44
+ export type HeadingListLayout = (typeof HEADING_LIST_LAYOUTS)[number];
45
+ export type StaggerRowLayout = (typeof STAGGER_ROW_LAYOUTS)[number];
46
+ /** What a missing or unknown `layout` resolves to, per composition. */
47
+ export declare const SECTION_LAYOUT_DEFAULTS: {
48
+ readonly Split: "media-end";
49
+ readonly HeadingList: "beside";
50
+ readonly StaggerRow: "staggered";
51
+ };
52
+ /** Sanity `_type` on the left, the reader's `kind` on the right; the same rows as schema-base's `SECTION_CONTRACTS`. */
53
+ export declare const READER_SECTION_KINDS: {
54
+ readonly featureStoriesBlock: "feature_stories";
55
+ readonly surfaceCardBlock: "cta";
56
+ readonly accentBandBlock: "accent_band";
57
+ readonly locationsBlock: "locations";
58
+ readonly textMediaBlock: "text_media";
59
+ readonly faqBlock: "faq";
60
+ readonly galleryBlock: "gallery";
61
+ readonly contactBlock: "contact";
62
+ };
63
+ /** A gallery is a `StaggerRow`, which holds two to four items. */
64
+ export declare const READER_GALLERY_PHOTO_MIN = 2;
65
+ export declare const READER_GALLERY_PHOTO_LIMIT = 4;
66
+ export declare const SECTION_WARNINGS: {
67
+ readonly layoutDefaulted: "section.layout.defaulted";
68
+ readonly unknownType: "section.type.unknown";
69
+ readonly imageMissingAlt: "section.image.missing-alt";
70
+ readonly imageMissingAsset: "section.image.missing-asset";
71
+ readonly emptySkipped: "section.empty.skipped";
72
+ };
73
+ export type SectionWarningCode = (typeof SECTION_WARNINGS)[keyof typeof SECTION_WARNINGS];
74
+ /** A named warning and the path in the stored document it is about. Never copy. */
75
+ export type SectionWarning = {
76
+ readonly code: SectionWarningCode;
77
+ readonly where: string;
78
+ };
79
+ /** A Sanity image as a site needs it. `asset` is the image asset's id; `alt` is never empty. */
80
+ export type SectionImageInput = {
81
+ readonly asset: string;
82
+ readonly alt: string;
83
+ readonly caption: string | null;
84
+ readonly hotspot: {
85
+ readonly x: number;
86
+ readonly y: number;
87
+ } | null;
88
+ };
89
+ export type TextMediaSectionInput = {
90
+ readonly kind: "text_media";
91
+ readonly anchor: string | null;
92
+ readonly layout: SplitLayout;
93
+ readonly heading: CopyInput;
94
+ readonly tail: CopyInput | null;
95
+ readonly body: CopyInput;
96
+ readonly action: ActionInput | null;
97
+ /** `null` only when the stored photo was dropped (no alt text or no asset); a warning names it. */
98
+ readonly image: SectionImageInput | null;
99
+ };
100
+ export type FaqSectionInput = {
101
+ readonly kind: "faq";
102
+ readonly anchor: string | null;
103
+ readonly layout: HeadingListLayout;
104
+ readonly heading: CopyInput;
105
+ readonly tail: CopyInput | null;
106
+ readonly intro: CopyInput | null;
107
+ readonly items: readonly {
108
+ readonly question: CopyInput;
109
+ readonly answer: CopyInput;
110
+ }[];
111
+ };
112
+ export type GallerySectionInput = {
113
+ readonly kind: "gallery";
114
+ readonly anchor: string | null;
115
+ readonly layout: StaggerRowLayout;
116
+ readonly heading: CopyInput | null;
117
+ readonly images: readonly SectionImageInput[];
118
+ };
119
+ export type ContactSectionInput = {
120
+ readonly kind: "contact";
121
+ readonly anchor: string | null;
122
+ readonly layout: SplitLayout;
123
+ readonly heading: CopyInput;
124
+ readonly body: CopyInput | null;
125
+ /** Show the site settings' email address. */
126
+ readonly showEmail: boolean;
127
+ /** Show the site settings' phone number. */
128
+ readonly showPhone: boolean;
129
+ readonly action: ActionInput | null;
130
+ readonly image: SectionImageInput | null;
131
+ };
132
+ export type NewSectionInput = TextMediaSectionInput | FaqSectionInput | GallerySectionInput | ContactSectionInput;
133
+ /** One entry of `PageContent.sections`: a home section kind or one of the new kinds. */
134
+ export type PageSectionInput = Exclude<CompositionBlockInput, {
135
+ readonly kind: "campaign";
136
+ }> | NewSectionInput;
@@ -54,6 +54,11 @@
54
54
  * accentBandBlock kind `accent_band`
55
55
  * locationsBlock kind `locations` (jumpLink → anchor)
56
56
  *
57
+ * A content page's `sections[]` (A-1915) reads those four kinds plus
58
+ * `text_media`, `faq`, `gallery` and `contact` into `PageContent.sections`,
59
+ * with their named warnings in `PageContent.warnings`. `sections.ts` holds the
60
+ * kinds, the A-1876 layout names and the warning codes.
61
+ *
57
62
  * Two places the composition cannot carry a proposed marker: a `cta` block's
58
63
  * plain strings, and the campaign's title and body (which travel as Markdown).
59
64
  * There the text is returned plain and the line is still listed in
@@ -61,6 +66,7 @@
61
66
  * approval. Nothing is dropped silently.
62
67
  */
63
68
  import { type CmsQuery } from "../contracts/query.cjs";
69
+ import { type PageSectionInput, type SectionWarning } from "./sections.cjs";
64
70
  export type CopyInput = string | {
65
71
  readonly text: string;
66
72
  readonly copySource: "captured" | "proposed";
@@ -142,6 +148,13 @@ export type PageContent = {
142
148
  readonly body: string;
143
149
  readonly migrated: boolean;
144
150
  readonly proposedCopy: readonly ProposedCopy[];
151
+ /**
152
+ * A-1915. Present only when the stored page has a `sections` list, so a page
153
+ * written before sections existed reads exactly as it did in 0.2.0.
154
+ */
155
+ readonly sections?: readonly PageSectionInput[];
156
+ /** A-1915. Present with `sections`: every named section warning, with its path. */
157
+ readonly warnings?: readonly SectionWarning[];
145
158
  };
146
159
  export type SiteSettingsContent = {
147
160
  readonly name: string;
@@ -54,6 +54,11 @@
54
54
  * accentBandBlock kind `accent_band`
55
55
  * locationsBlock kind `locations` (jumpLink → anchor)
56
56
  *
57
+ * A content page's `sections[]` (A-1915) reads those four kinds plus
58
+ * `text_media`, `faq`, `gallery` and `contact` into `PageContent.sections`,
59
+ * with their named warnings in `PageContent.warnings`. `sections.ts` holds the
60
+ * kinds, the A-1876 layout names and the warning codes.
61
+ *
57
62
  * Two places the composition cannot carry a proposed marker: a `cta` block's
58
63
  * plain strings, and the campaign's title and body (which travel as Markdown).
59
64
  * There the text is returned plain and the line is still listed in
@@ -61,6 +66,7 @@
61
66
  * approval. Nothing is dropped silently.
62
67
  */
63
68
  import { type CmsQuery } from "../contracts/query.mjs";
69
+ import { type PageSectionInput, type SectionWarning } from "./sections.mjs";
64
70
  export type CopyInput = string | {
65
71
  readonly text: string;
66
72
  readonly copySource: "captured" | "proposed";
@@ -142,6 +148,13 @@ export type PageContent = {
142
148
  readonly body: string;
143
149
  readonly migrated: boolean;
144
150
  readonly proposedCopy: readonly ProposedCopy[];
151
+ /**
152
+ * A-1915. Present only when the stored page has a `sections` list, so a page
153
+ * written before sections existed reads exactly as it did in 0.2.0.
154
+ */
155
+ readonly sections?: readonly PageSectionInput[];
156
+ /** A-1915. Present with `sections`: every named section warning, with its path. */
157
+ readonly warnings?: readonly SectionWarning[];
145
158
  };
146
159
  export type SiteSettingsContent = {
147
160
  readonly name: string;
@@ -54,6 +54,11 @@
54
54
  * accentBandBlock kind `accent_band`
55
55
  * locationsBlock kind `locations` (jumpLink → anchor)
56
56
  *
57
+ * A content page's `sections[]` (A-1915) reads those four kinds plus
58
+ * `text_media`, `faq`, `gallery` and `contact` into `PageContent.sections`,
59
+ * with their named warnings in `PageContent.warnings`. `sections.ts` holds the
60
+ * kinds, the A-1876 layout names and the warning codes.
61
+ *
57
62
  * Two places the composition cannot carry a proposed marker: a `cta` block's
58
63
  * plain strings, and the campaign's title and body (which travel as Markdown).
59
64
  * There the text is returned plain and the line is still listed in
@@ -61,6 +66,7 @@
61
66
  * approval. Nothing is dropped silently.
62
67
  */
63
68
  import { type CmsQuery } from "../contracts/query.js";
69
+ import { type PageSectionInput, type SectionWarning } from "./sections.js";
64
70
  export type CopyInput = string | {
65
71
  readonly text: string;
66
72
  readonly copySource: "captured" | "proposed";
@@ -142,6 +148,13 @@ export type PageContent = {
142
148
  readonly body: string;
143
149
  readonly migrated: boolean;
144
150
  readonly proposedCopy: readonly ProposedCopy[];
151
+ /**
152
+ * A-1915. Present only when the stored page has a `sections` list, so a page
153
+ * written before sections existed reads exactly as it did in 0.2.0.
154
+ */
155
+ readonly sections?: readonly PageSectionInput[];
156
+ /** A-1915. Present with `sections`: every named section warning, with its path. */
157
+ readonly warnings?: readonly SectionWarning[];
145
158
  };
146
159
  export type SiteSettingsContent = {
147
160
  readonly name: string;