@aglyn/plugins-ai 1.0.0-beta.263 → 1.0.0-beta.266
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/package.json +17 -17
- package/src/lib/assets/site-styles/beauty.webp +0 -0
- package/src/lib/assets/site-styles/blog.webp +0 -0
- package/src/lib/assets/site-styles/business.webp +0 -0
- package/src/lib/assets/site-styles/coming-soon.webp +0 -0
- package/src/lib/assets/site-styles/education.webp +0 -0
- package/src/lib/assets/site-styles/events.webp +0 -0
- package/src/lib/assets/site-styles/fitness.webp +0 -0
- package/src/lib/assets/site-styles/landing.webp +0 -0
- package/src/lib/assets/site-styles/music.webp +0 -0
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/personal.webp +0 -0
- package/src/lib/assets/site-styles/photography.webp +0 -0
- package/src/lib/assets/site-styles/portfolio.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/realestate.webp +0 -0
- package/src/lib/assets/site-styles/restaurant.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/studio.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/assets/site-styles/wellness.webp +0 -0
- package/src/lib/assets/site-styles/yoga.webp +0 -0
- package/src/lib/components/ai-site-start-card.component.d.ts +12 -0
- package/src/lib/components/ai-site-start-card.component.js +256 -236
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +108 -0
- package/src/lib/components/ai-site-start-steps.component.js +451 -0
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -0
- package/src/lib/components/ai-site-start-tracking.d.ts +114 -0
- package/src/lib/components/ai-site-start-tracking.js +277 -0
- package/src/lib/components/ai-site-start-tracking.js.map +1 -0
- package/src/lib/components/ai-site-style-previews.d.ts +26 -0
- package/src/lib/components/ai-site-style-previews.js +115 -0
- package/src/lib/components/ai-site-style-previews.js.map +1 -0
- package/src/lib/insights/ai-insight-digest.d.ts +9 -0
- package/src/lib/insights/ai-insight-digest.js +35 -11
- package/src/lib/insights/ai-insight-digest.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
- package/src/lib/jobs/ai-job-page-language.js +20 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +8 -2
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +43 -8
- package/src/lib/jobs/ai-job-plan-step.js +99 -18
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +93 -2
- package/src/lib/jobs/ai-job-site-datasets.js +308 -21
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +191 -13
- package/src/lib/jobs/ai-layout-stock-photos.js +1709 -69
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.d.ts +58 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js +678 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js +270 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js +277 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.d.ts +23 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.js +163 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.d.ts +23 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.js +53 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.js +119 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +54 -15
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
- package/src/lib/layout-language/ai-layout-design.js +14 -13
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +55 -0
- package/src/lib/layout-language/ai-layout-listings.js +103 -5
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +42 -8
- package/src/lib/layout-language/ai-layout-pictures.js +91 -6
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-site-home-arcs.d.ts +75 -0
- package/src/lib/model/ai-site-home-arcs.js +741 -0
- package/src/lib/model/ai-site-home-arcs.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +18 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +9 -6
- package/src/lib/model/ai-site-job.js +8 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +24 -24
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-start-steps.d.ts +67 -0
- package/src/lib/model/ai-site-start-steps.js +127 -0
- package/src/lib/model/ai-site-start-steps.js.map +1 -0
- package/src/lib/model/ai-site-start.js +4 -1
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -0
- package/src/lib/runtime/ai-doctrine-validators.js +119 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -1
- package/src/lib/runtime/ai-doctrine.js +9 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +5 -2
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +6 -0
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-insight-digest-route.js +2 -2
- package/src/lib/server/ai-insight-digest-route.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +3 -0
- package/src/lib/usage/assist-free-taste.js.map +1 -1
|
@@ -102,9 +102,12 @@ type Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'sta
|
|
|
102
102
|
/** The family a kind's pages are drawn in. */
|
|
103
103
|
export declare function aiLayoutFamily(kind: string): Family;
|
|
104
104
|
/**
|
|
105
|
-
* A site's choices, the same for every page of it
|
|
106
|
-
*
|
|
107
|
-
*
|
|
105
|
+
* A site's choices, the same for every page of it, each drawn at even odds
|
|
106
|
+
* among the options that suit the kind (AGL-3899), so two sites of one kind
|
|
107
|
+
* are drawn apart. Favoring each list's first option made the newest
|
|
108
|
+
* production sites one design: the kind's first hero won 55% before the
|
|
109
|
+
* pick, a business site opened split, groups were ruled two times in three
|
|
110
|
+
* and every text section took a photo.
|
|
108
111
|
*/
|
|
109
112
|
export declare function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices;
|
|
110
113
|
/**
|
|
@@ -137,23 +137,22 @@ const pick = (next, list)=>list[Math.floor(next() * list.length) % list.length];
|
|
|
137
137
|
return (_FAMILIES_kind = FAMILIES[kind]) != null ? _FAMILIES_kind : 'standard';
|
|
138
138
|
}
|
|
139
139
|
/**
|
|
140
|
-
* A site's choices, the same for every page of it
|
|
141
|
-
*
|
|
142
|
-
*
|
|
140
|
+
* A site's choices, the same for every page of it, each drawn at even odds
|
|
141
|
+
* among the options that suit the kind (AGL-3899), so two sites of one kind
|
|
142
|
+
* are drawn apart. Favoring each list's first option made the newest
|
|
143
|
+
* production sites one design: the kind's first hero won 55% before the
|
|
144
|
+
* pick, a business site opened split, groups were ruled two times in three
|
|
145
|
+
* and every text section took a photo.
|
|
143
146
|
*/ export function aiLayoutDesignChoices(design) {
|
|
144
147
|
var _HEROES_design_kind;
|
|
145
148
|
const next = random(design.seed ^ 0x2f6b9d1);
|
|
146
149
|
const family = aiLayoutFamily(design.kind);
|
|
147
150
|
const heroes = (_HEROES_design_kind = HEROES[design.kind]) != null ? _HEROES_design_kind : DEFAULT_HEROES;
|
|
148
|
-
const hero =
|
|
149
|
-
const group =
|
|
151
|
+
const hero = pick(next, heroes);
|
|
152
|
+
const group = pick(next, [
|
|
150
153
|
'cards',
|
|
151
|
-
'cards',
|
|
152
|
-
'ruled'
|
|
153
|
-
]) : pick(next, [
|
|
154
154
|
'ruled',
|
|
155
|
-
'
|
|
156
|
-
'ruled'
|
|
155
|
+
'icons'
|
|
157
156
|
]);
|
|
158
157
|
return {
|
|
159
158
|
hero,
|
|
@@ -165,10 +164,12 @@ const pick = (next, list)=>list[Math.floor(next() * list.length) % list.length];
|
|
|
165
164
|
group: family === 'retail' ? 'icons' : group,
|
|
166
165
|
steps: pick(next, [
|
|
167
166
|
'timeline',
|
|
168
|
-
'numbers'
|
|
169
|
-
'timeline'
|
|
167
|
+
'numbers'
|
|
170
168
|
]),
|
|
171
|
-
|
|
169
|
+
// Whether a section of words takes a photo beside it: some sites are
|
|
170
|
+
// photo-led, some let their type carry the page. A storefront never
|
|
171
|
+
// shows a band of text alone (AGL-3676), so its story always takes one.
|
|
172
|
+
features: next() < 0.6 || family === 'retail',
|
|
172
173
|
featureLeft: next() < 0.5,
|
|
173
174
|
coverClose: heroes.includes('cover') && next() < 0.7,
|
|
174
175
|
splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-design.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model\n * described in the layout language is ARRANGED, decided by code from the\n * site's kind and its job's seed — never asked of the model, so a recorded\n * answer compiles into the new design with no new call.\n *\n * Why. A page compiled one way for every site read like a SaaS landing page:\n * a centered text hero, three outlined cards, a numbered row, a solid form\n * band. The designer templates people compare a site to (Squarespace's Loam,\n * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are\n * built from a handful of moves instead — a full-bleed photo hero with the\n * type over it, image and text side by side, work shown as pictures in an\n * uneven grid, writing as a list under thin rules, a closing band over a\n * photo — and they pick those moves for the KIND of site.\n *\n * So a site's design is a set of VARIANTS — the hero's arrangement, how its\n * groups are drawn, whether its text sections take a picture beside them,\n * how its closing section reads — each chosen among the ones that suit the\n * kind, by the seed. The choices are the site's: every page of one site is\n * drawn with the same hero and the same group style, so the site reads as\n * one design, and two sites of one kind come out differently.\n *\n * This is NOT a template library. A variant is a way of drawing whatever the\n * model wrote; the sections, their order, their bands, their words and how\n * many items each shows are still the model's, page by page.\n *\n * Pure: no tree, no theme, no network.\n */\n\n/** What the compiler is told about the site a page belongs to. */\nexport interface AiLayoutDesign {\n /** The site's kind (`AI_SITE_KINDS`); an unknown one draws as a business site. */\n kind: string\n /** The job's design seed: the same for every page of one site. */\n seed: number\n /** Whether this page is the site's home, which opens tallest. */\n home: boolean\n}\n\n/**\n * How the first section of a page is drawn:\n * - `cover`: a full-bleed photo with the words over it on a scrim;\n * - `split`: the words beside a tall photo, inside the page's measure;\n * - `editorial`: display type across the page, the lede and the buttons in\n * a row under it, and a wide photo below.\n */\nexport type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial'\n\n/**\n * How a group of cards is drawn where no component draws it:\n * - `cards`: the theme's cards (the compiler's first design);\n * - `ruled`: open columns, each under a thin rule, no card around it;\n * - `pictures`: a picture over each item's title and caption, in an uneven\n * grid (work, products);\n * - `articles`: a picture over each title and summary, in even columns (writing);\n * - `menu`: two columns of lines under rules (dishes, prices left out);\n * - `icons`: open columns, each led by an icon in a soft disc over its title\n * and words — a storefront's reasons to buy (shipping, small batch,\n * returns), never a block of text (AGL-3676).\n */\nexport type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu' | 'icons'\n\n/** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */\nexport type AiLayoutStepsVariant = 'numbers' | 'timeline'\n\n/** The site-level choices a page is drawn with. */\nexport interface AiLayoutDesignChoices {\n hero: AiLayoutHeroVariant\n /** The group style of a section that names no subject the kind draws its own way. */\n group: 'cards' | 'ruled' | 'icons'\n steps: AiLayoutStepsVariant\n /** Whether a section of words takes a photo beside it. */\n features: boolean\n /** Whether the photo of the first such section sits on the left. */\n featureLeft: boolean\n /** Whether a closing section of words is drawn over a photo. */\n coverClose: boolean\n /** Whether a section's heading sits beside its items rather than over them. */\n splitHeads: boolean\n /** How work is laid out where the kind shows it as pictures. */\n pictures: 'mosaic' | 'even'\n /**\n * How a blog's own posts are listed where a page shows them (AGL-3676):\n * `grid`, a cover over each post's date, title and excerpt in columns; or\n * `ruled`, a list under thin rules, the date in its own column and a small\n * cover beside each title — the two ways an editorial site lists writing.\n */\n writing: 'grid' | 'ruled'\n}\n\ntype Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard'\n\nconst FAMILIES: Readonly<Record<string, Family>> = {\n portfolio: 'gallery',\n photography: 'gallery',\n studio: 'gallery',\n blog: 'editorial',\n restaurant: 'hospitality',\n events: 'hospitality',\n yoga: 'calm',\n wellness: 'calm',\n beauty: 'calm',\n store: 'retail',\n}\n\n/** The heroes each kind opens with, best suited first. */\nconst HEROES: Readonly<Record<string, readonly AiLayoutHeroVariant[]>> = {\n photography: ['cover', 'editorial'],\n portfolio: ['editorial', 'split'],\n studio: ['editorial', 'cover'],\n blog: ['editorial', 'cover', 'split'],\n restaurant: ['cover', 'split'],\n events: ['cover', 'split'],\n fitness: ['cover', 'split'],\n music: ['cover', 'editorial'],\n nonprofit: ['cover', 'split'],\n realestate: ['cover', 'split'],\n trades: ['cover', 'split'],\n yoga: ['split', 'cover'],\n wellness: ['split', 'cover'],\n beauty: ['split', 'cover', 'editorial'],\n store: ['split', 'editorial'],\n}\nconst DEFAULT_HEROES: readonly AiLayoutHeroVariant[] = ['split', 'cover', 'editorial']\n\n/** A small deterministic generator over a seed (mulberry32). */\nfunction random(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n\n/** The family a kind's pages are drawn in. */\nexport function aiLayoutFamily(kind: string): Family {\n return FAMILIES[kind] ?? 'standard'\n}\n\n/**\n * A site's choices, the same for every page of it. The first option of each\n * list is the kind's best, so it is favored; the seed still turns it, so two\n * sites of one kind are drawn apart.\n */\nexport function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices {\n const next = random(design.seed ^ 0x2f6b9d1)\n const family = aiLayoutFamily(design.kind)\n const heroes = HEROES[design.kind] ?? DEFAULT_HEROES\n const hero = next() < 0.55 ? (heroes[0] as AiLayoutHeroVariant) : pick(next, heroes)\n const group = family === 'calm' ? pick(next, ['cards', 'cards', 'ruled'] as const) : pick(next, ['ruled', 'cards', 'ruled'] as const)\n return {\n hero,\n // A store's reasons to buy — the making, the shipping, the care — read\n // as open columns each led by an icon, the way a storefront theme draws\n // them: never a SaaS page's feature cards, and never the plain columns\n // of a title and a sentence the beta.237 Willow Wick home showed four\n // bands of (AGL-3676).\n group: family === 'retail' ? 'icons' : group,\n steps: pick(next, ['timeline', 'numbers', 'timeline'] as const),\n features: true,\n featureLeft: next() < 0.5,\n coverClose: heroes.includes('cover') && next() < 0.7,\n splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,\n pictures: design.kind === 'photography' ? pick(next, ['even', 'mosaic'] as const) : pick(next, ['mosaic', 'mosaic', 'even'] as const),\n // Drawn last, so every choice above stays what it was for a seed.\n writing: pick(next, ['grid', 'ruled'] as const),\n }\n}\n\nconst WORK = /\\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\\b/i\nconst WRITING = /\\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\\b/i\nconst DISHES = /\\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\\b/i\n// A store's own products are listed by its Product grid (AGL-3676), never drawn\n// from words: what a store shows as pictures is how its range is grouped — its\n// collections, gift sets, occasions and scents. \"Candle care and burn notes\" is no band of pictures.\nconst CATEGORIES = /\\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\\b/i\n\n/**\n * How a section's group of cards is drawn, from what the section is about:\n * work in a gallery kind and products in a store are pictures, writing in a\n * blog is articles, dishes in a restaurant a menu; anything else takes the\n * site's own group style.\n */\nexport function aiLayoutGroupVariant(\n design: AiLayoutDesign,\n choices: AiLayoutDesignChoices,\n words: string,\n): AiLayoutGroupVariant {\n const family = aiLayoutFamily(design.kind)\n if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures'\n if (family === 'editorial' && WRITING.test(words)) return 'articles'\n if (family === 'hospitality' && DISHES.test(words)) return 'menu'\n if (family === 'retail' && CATEGORIES.test(words)) return 'pictures'\n return choices.group\n}\n"],"names":["FAMILIES","portfolio","photography","studio","blog","restaurant","events","yoga","wellness","beauty","store","HEROES","fitness","music","nonprofit","realestate","trades","DEFAULT_HEROES","random","seed","state","t","Math","imul","pick","next","list","floor","length","aiLayoutFamily","kind","aiLayoutDesignChoices","design","family","heroes","hero","group","steps","features","featureLeft","coverClose","includes","splitHeads","pictures","writing","WORK","WRITING","DISHES","CATEGORIES","aiLayoutGroupVariant","choices","words","test"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-design.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model\n * described in the layout language is ARRANGED, decided by code from the\n * site's kind and its job's seed — never asked of the model, so a recorded\n * answer compiles into the new design with no new call.\n *\n * Why. A page compiled one way for every site read like a SaaS landing page:\n * a centered text hero, three outlined cards, a numbered row, a solid form\n * band. The designer templates people compare a site to (Squarespace's Loam,\n * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are\n * built from a handful of moves instead — a full-bleed photo hero with the\n * type over it, image and text side by side, work shown as pictures in an\n * uneven grid, writing as a list under thin rules, a closing band over a\n * photo — and they pick those moves for the KIND of site.\n *\n * So a site's design is a set of VARIANTS — the hero's arrangement, how its\n * groups are drawn, whether its text sections take a picture beside them,\n * how its closing section reads — each chosen among the ones that suit the\n * kind, by the seed. The choices are the site's: every page of one site is\n * drawn with the same hero and the same group style, so the site reads as\n * one design, and two sites of one kind come out differently.\n *\n * This is NOT a template library. A variant is a way of drawing whatever the\n * model wrote; the sections, their order, their bands, their words and how\n * many items each shows are still the model's, page by page.\n *\n * Pure: no tree, no theme, no network.\n */\n\n/** What the compiler is told about the site a page belongs to. */\nexport interface AiLayoutDesign {\n /** The site's kind (`AI_SITE_KINDS`); an unknown one draws as a business site. */\n kind: string\n /** The job's design seed: the same for every page of one site. */\n seed: number\n /** Whether this page is the site's home, which opens tallest. */\n home: boolean\n}\n\n/**\n * How the first section of a page is drawn:\n * - `cover`: a full-bleed photo with the words over it on a scrim;\n * - `split`: the words beside a tall photo, inside the page's measure;\n * - `editorial`: display type across the page, the lede and the buttons in\n * a row under it, and a wide photo below.\n */\nexport type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial'\n\n/**\n * How a group of cards is drawn where no component draws it:\n * - `cards`: the theme's cards (the compiler's first design);\n * - `ruled`: open columns, each under a thin rule, no card around it;\n * - `pictures`: a picture over each item's title and caption, in an uneven\n * grid (work, products);\n * - `articles`: a picture over each title and summary, in even columns (writing);\n * - `menu`: two columns of lines under rules (dishes, prices left out);\n * - `icons`: open columns, each led by an icon in a soft disc over its title\n * and words — a storefront's reasons to buy (shipping, small batch,\n * returns), never a block of text (AGL-3676).\n */\nexport type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu' | 'icons'\n\n/** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */\nexport type AiLayoutStepsVariant = 'numbers' | 'timeline'\n\n/** The site-level choices a page is drawn with. */\nexport interface AiLayoutDesignChoices {\n hero: AiLayoutHeroVariant\n /** The group style of a section that names no subject the kind draws its own way. */\n group: 'cards' | 'ruled' | 'icons'\n steps: AiLayoutStepsVariant\n /** Whether a section of words takes a photo beside it. */\n features: boolean\n /** Whether the photo of the first such section sits on the left. */\n featureLeft: boolean\n /** Whether a closing section of words is drawn over a photo. */\n coverClose: boolean\n /** Whether a section's heading sits beside its items rather than over them. */\n splitHeads: boolean\n /** How work is laid out where the kind shows it as pictures. */\n pictures: 'mosaic' | 'even'\n /**\n * How a blog's own posts are listed where a page shows them (AGL-3676):\n * `grid`, a cover over each post's date, title and excerpt in columns; or\n * `ruled`, a list under thin rules, the date in its own column and a small\n * cover beside each title — the two ways an editorial site lists writing.\n */\n writing: 'grid' | 'ruled'\n}\n\ntype Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard'\n\nconst FAMILIES: Readonly<Record<string, Family>> = {\n portfolio: 'gallery',\n photography: 'gallery',\n studio: 'gallery',\n blog: 'editorial',\n restaurant: 'hospitality',\n events: 'hospitality',\n yoga: 'calm',\n wellness: 'calm',\n beauty: 'calm',\n store: 'retail',\n}\n\n/** The heroes each kind opens with, best suited first. */\nconst HEROES: Readonly<Record<string, readonly AiLayoutHeroVariant[]>> = {\n photography: ['cover', 'editorial'],\n portfolio: ['editorial', 'split'],\n studio: ['editorial', 'cover'],\n blog: ['editorial', 'cover', 'split'],\n restaurant: ['cover', 'split'],\n events: ['cover', 'split'],\n fitness: ['cover', 'split'],\n music: ['cover', 'editorial'],\n nonprofit: ['cover', 'split'],\n realestate: ['cover', 'split'],\n trades: ['cover', 'split'],\n yoga: ['split', 'cover'],\n wellness: ['split', 'cover'],\n beauty: ['split', 'cover', 'editorial'],\n store: ['split', 'editorial'],\n}\nconst DEFAULT_HEROES: readonly AiLayoutHeroVariant[] = ['split', 'cover', 'editorial']\n\n/** A small deterministic generator over a seed (mulberry32). */\nfunction random(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n\n/** The family a kind's pages are drawn in. */\nexport function aiLayoutFamily(kind: string): Family {\n return FAMILIES[kind] ?? 'standard'\n}\n\n/**\n * A site's choices, the same for every page of it, each drawn at even odds\n * among the options that suit the kind (AGL-3899), so two sites of one kind\n * are drawn apart. Favoring each list's first option made the newest\n * production sites one design: the kind's first hero won 55% before the\n * pick, a business site opened split, groups were ruled two times in three\n * and every text section took a photo.\n */\nexport function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices {\n const next = random(design.seed ^ 0x2f6b9d1)\n const family = aiLayoutFamily(design.kind)\n const heroes = HEROES[design.kind] ?? DEFAULT_HEROES\n const hero = pick(next, heroes)\n const group = pick(next, ['cards', 'ruled', 'icons'] as const)\n return {\n hero,\n // A store's reasons to buy — the making, the shipping, the care — read\n // as open columns each led by an icon, the way a storefront theme draws\n // them: never a SaaS page's feature cards, and never the plain columns\n // of a title and a sentence the beta.237 Willow Wick home showed four\n // bands of (AGL-3676).\n group: family === 'retail' ? 'icons' : group,\n steps: pick(next, ['timeline', 'numbers'] as const),\n // Whether a section of words takes a photo beside it: some sites are\n // photo-led, some let their type carry the page. A storefront never\n // shows a band of text alone (AGL-3676), so its story always takes one.\n features: next() < 0.6 || family === 'retail',\n featureLeft: next() < 0.5,\n coverClose: heroes.includes('cover') && next() < 0.7,\n splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,\n pictures: design.kind === 'photography' ? pick(next, ['even', 'mosaic'] as const) : pick(next, ['mosaic', 'mosaic', 'even'] as const),\n // Drawn last, so every choice above stays what it was for a seed.\n writing: pick(next, ['grid', 'ruled'] as const),\n }\n}\n\nconst WORK = /\\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\\b/i\nconst WRITING = /\\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\\b/i\nconst DISHES = /\\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\\b/i\n// A store's own products are listed by its Product grid (AGL-3676), never drawn\n// from words: what a store shows as pictures is how its range is grouped — its\n// collections, gift sets, occasions and scents. \"Candle care and burn notes\" is no band of pictures.\nconst CATEGORIES = /\\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\\b/i\n\n/**\n * How a section's group of cards is drawn, from what the section is about:\n * work in a gallery kind and products in a store are pictures, writing in a\n * blog is articles, dishes in a restaurant a menu; anything else takes the\n * site's own group style.\n */\nexport function aiLayoutGroupVariant(\n design: AiLayoutDesign,\n choices: AiLayoutDesignChoices,\n words: string,\n): AiLayoutGroupVariant {\n const family = aiLayoutFamily(design.kind)\n if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures'\n if (family === 'editorial' && WRITING.test(words)) return 'articles'\n if (family === 'hospitality' && DISHES.test(words)) return 'menu'\n if (family === 'retail' && CATEGORIES.test(words)) return 'pictures'\n return choices.group\n}\n"],"names":["FAMILIES","portfolio","photography","studio","blog","restaurant","events","yoga","wellness","beauty","store","HEROES","fitness","music","nonprofit","realestate","trades","DEFAULT_HEROES","random","seed","state","t","Math","imul","pick","next","list","floor","length","aiLayoutFamily","kind","aiLayoutDesignChoices","design","family","heroes","hero","group","steps","features","featureLeft","coverClose","includes","splitHeads","pictures","writing","WORK","WRITING","DISHES","CATEGORIES","aiLayoutGroupVariant","choices","words","test"],"mappings":"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"}
|
|
@@ -83,6 +83,19 @@ export interface AiLayoutListingPlacement {
|
|
|
83
83
|
/** The section, by plan index. */
|
|
84
84
|
section: number;
|
|
85
85
|
role: AiLayoutListingRole;
|
|
86
|
+
/**
|
|
87
|
+
* The one group of a dataset's records the section lists (AGL-3921), where
|
|
88
|
+
* the page lists the dataset in several sections each named for one value
|
|
89
|
+
* of its category-like field: "Family portrait gallery" lists the records
|
|
90
|
+
* filed "Family portraits", drawn as the repeat's `repeatFilter`.
|
|
91
|
+
*/
|
|
92
|
+
filter?: AiLayoutListingFilter;
|
|
93
|
+
}
|
|
94
|
+
/** A records placement's group: the field, its value, and the records filed under it, by name. */
|
|
95
|
+
export interface AiLayoutListingFilter {
|
|
96
|
+
field: string;
|
|
97
|
+
value: string;
|
|
98
|
+
records: string[];
|
|
86
99
|
}
|
|
87
100
|
/** One field of a dataset a `records` listing shows, as its card binds it. */
|
|
88
101
|
export interface AiLayoutListingField {
|
|
@@ -175,7 +188,10 @@ export declare function aiLayoutListingsOf(inputs: Readonly<Record<string, unkno
|
|
|
175
188
|
export declare function aiLayoutListingAt(listings: readonly AiLayoutListing[], screenId: string | null | undefined, section: number): {
|
|
176
189
|
listing: AiLayoutListing;
|
|
177
190
|
role: AiLayoutListingRole;
|
|
191
|
+
filter?: AiLayoutListingFilter;
|
|
178
192
|
} | null;
|
|
193
|
+
/** The repeat filter a placement's group is drawn as: `category == Family portraits`. */
|
|
194
|
+
export declare function aiLayoutRepeatFilterOf(filter: Pick<AiLayoutListingFilter, 'field' | 'value'>): string;
|
|
179
195
|
/** The plan of a page, as a placement is chosen from it. */
|
|
180
196
|
export interface AiLayoutListingScreen {
|
|
181
197
|
id?: string | null;
|
|
@@ -246,7 +262,39 @@ export declare function aiLayoutRecordBaseOf(dataset: {
|
|
|
246
262
|
export declare function aiLayoutRecordsPlacements(dataset: {
|
|
247
263
|
id: string;
|
|
248
264
|
name: string;
|
|
265
|
+
/** Its records, by name, as {@link groups} files them. */
|
|
266
|
+
records?: readonly string[];
|
|
267
|
+
/** What its records are filed under: the field, by id, and each record's value, by position. */
|
|
268
|
+
groups?: {
|
|
269
|
+
field: string;
|
|
270
|
+
values: readonly string[];
|
|
271
|
+
} | null;
|
|
249
272
|
}, screens: readonly AiLayoutListingScreen[]): AiLayoutListingPlacement[];
|
|
273
|
+
/**
|
|
274
|
+
* The value of a dataset's groups a section's name is named for (AGL-3921),
|
|
275
|
+
* or `null`: every word of the value is in the name ("Weddings and
|
|
276
|
+
* elopements gallery" is "Weddings and elopements"), else every word of the
|
|
277
|
+
* name past its framing is in the value ("Weddings" is "Weddings and
|
|
278
|
+
* elopements"), and no other value is named as well. Pure.
|
|
279
|
+
*/
|
|
280
|
+
export declare function aiLayoutSectionGroup(sectionName: string, values: readonly string[]): string | null;
|
|
281
|
+
/**
|
|
282
|
+
* A page's placements of one dataset, each section named for one of its
|
|
283
|
+
* groups listing only that group's records (AGL-3921). The beta.265 Laurel
|
|
284
|
+
* Ridge "Galleries" page planned "Weddings and elopements gallery" and
|
|
285
|
+
* "Family portrait gallery" over one "Gallery collections" whose records were
|
|
286
|
+
* filed under those two categories, and both sections listed all ten: the
|
|
287
|
+
* plan names no filter, so the build reads it off the sections' names. Only
|
|
288
|
+
* where the page lists the dataset more than once; a section named for no
|
|
289
|
+
* one group lists them all. Pure.
|
|
290
|
+
*/
|
|
291
|
+
export declare function aiLayoutGroupedPlacements(placements: readonly AiLayoutListingPlacement[], screen: Pick<AiLayoutListingScreen, 'sections'>, dataset: {
|
|
292
|
+
records?: readonly string[];
|
|
293
|
+
groups?: {
|
|
294
|
+
field: string;
|
|
295
|
+
values: readonly string[];
|
|
296
|
+
} | null;
|
|
297
|
+
}): AiLayoutListingPlacement[];
|
|
250
298
|
/**
|
|
251
299
|
* The sections that list the site's events (AGL-3755): every section whose
|
|
252
300
|
* plan names one of them in its `uses` (`new:<its name>`), once however many
|
|
@@ -254,3 +302,10 @@ export declare function aiLayoutRecordsPlacements(dataset: {
|
|
|
254
302
|
* all. Chosen from the plan, never asked of the model.
|
|
255
303
|
*/
|
|
256
304
|
export declare function aiLayoutEventsPlacements(names: readonly string[], screens: readonly AiLayoutListingScreen[]): AiLayoutListingPlacement[];
|
|
305
|
+
/** Whether a field's name says it holds what a record's photo shows (AGL-3856). */
|
|
306
|
+
export declare function aiLayoutIsRecordImageAlt(name: string): boolean;
|
|
307
|
+
/** The field holding what a record's photo shows, by id, or `null` (AGL-3856). */
|
|
308
|
+
export declare function aiLayoutRecordImageAltField(fields: readonly {
|
|
309
|
+
id: string;
|
|
310
|
+
name: string;
|
|
311
|
+
}[]): string | null;
|
|
@@ -91,6 +91,19 @@ const FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
|
91
91
|
] : [];
|
|
92
92
|
}).slice(0, 16);
|
|
93
93
|
}
|
|
94
|
+
/** A placement's group as its input carries it; one whose field or value does not read is none. */ function listingFilterOf(raw) {
|
|
95
|
+
const filter = raw != null ? raw : {};
|
|
96
|
+
const field = text(filter['field']);
|
|
97
|
+
// The repeat's filter is `field == value` on one line, at most 200 characters.
|
|
98
|
+
const value = text(filter['value']).replace(/\s+/g, ' ').slice(0, 120);
|
|
99
|
+
if (!FIELD_ID.test(field) || !value) return null;
|
|
100
|
+
const records = (Array.isArray(filter['records']) ? filter['records'] : []).map(text).filter(Boolean).slice(0, 12);
|
|
101
|
+
return {
|
|
102
|
+
field,
|
|
103
|
+
value,
|
|
104
|
+
records
|
|
105
|
+
};
|
|
106
|
+
}
|
|
94
107
|
/** A site's listings as its unit inputs carry them; nothing that does not read is kept. */ export function aiLayoutListingsOf(inputs) {
|
|
95
108
|
const raw = inputs == null ? void 0 : inputs[AI_LAYOUT_LISTINGS_INPUT];
|
|
96
109
|
if (!Array.isArray(raw)) return [];
|
|
@@ -123,12 +136,15 @@ const FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
|
123
136
|
const screenId = text(placement['screenId']);
|
|
124
137
|
const section = placement['section'];
|
|
125
138
|
const role = placement['role'];
|
|
139
|
+
const filter = kind === 'records' ? listingFilterOf(placement['filter']) : null;
|
|
126
140
|
return screenId && typeof section === 'number' && Number.isInteger(section) && section >= 0 && PLACEMENT_ROLES.includes(role) ? [
|
|
127
|
-
{
|
|
141
|
+
_extends({
|
|
128
142
|
screenId,
|
|
129
143
|
section,
|
|
130
144
|
role
|
|
131
|
-
}
|
|
145
|
+
}, filter ? {
|
|
146
|
+
filter
|
|
147
|
+
} : {})
|
|
132
148
|
] : [];
|
|
133
149
|
});
|
|
134
150
|
listings.push(_extends({
|
|
@@ -168,13 +184,18 @@ const FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
|
168
184
|
if (!screenId) return null;
|
|
169
185
|
for (const listing of listings){
|
|
170
186
|
const placement = listing.placements.find((entry)=>entry.screenId === screenId && entry.section === section);
|
|
171
|
-
if (placement) return {
|
|
187
|
+
if (placement) return _extends({
|
|
172
188
|
listing,
|
|
173
189
|
role: placement.role
|
|
174
|
-
}
|
|
190
|
+
}, placement.filter ? {
|
|
191
|
+
filter: placement.filter
|
|
192
|
+
} : {});
|
|
175
193
|
}
|
|
176
194
|
return null;
|
|
177
195
|
}
|
|
196
|
+
/** The repeat filter a placement's group is drawn as: `category == Family portraits`. */ export function aiLayoutRepeatFilterOf(filter) {
|
|
197
|
+
return `${filter.field} == ${filter.value}`;
|
|
198
|
+
}
|
|
178
199
|
const PRODUCT_PAGE = /\b(shop|store|products?|catalog(?:ue)?|collections?|range|browse)\b/i;
|
|
179
200
|
const PRODUCT_SECTION = /\b(shop|store|products?|catalog(?:ue)?|collections?|range|bestsellers?|best[- ]sellers?|new arrivals|arrivals|featured|favou?rites|signature)\b/i;
|
|
180
201
|
const POST_SECTION = /\b(blog|posts?|articles?|writing|journal|stories|essays?|latest|recent|news|featured)\b/i;
|
|
@@ -335,18 +356,83 @@ const isHome = (slug)=>firstSegment(slug) === '';
|
|
|
335
356
|
const placements = [];
|
|
336
357
|
for (const screen of screens){
|
|
337
358
|
if (typeof screen.id !== 'string' || !screen.id) continue;
|
|
359
|
+
const here = [];
|
|
338
360
|
screen.sections.forEach((section, index)=>{
|
|
339
361
|
var _section_uses;
|
|
340
362
|
const lists = ((_section_uses = section.uses) != null ? _section_uses : []).some((ref)=>ref === dataset.id || ref.trim().toLowerCase() === named);
|
|
341
|
-
if (lists)
|
|
363
|
+
if (lists) here.push({
|
|
342
364
|
screenId: screen.id,
|
|
343
365
|
section: index,
|
|
344
366
|
role: isHome(screen.slug) ? 'featured' : 'index'
|
|
345
367
|
});
|
|
346
368
|
});
|
|
369
|
+
placements.push(...aiLayoutGroupedPlacements(here, screen, dataset));
|
|
347
370
|
}
|
|
348
371
|
return placements;
|
|
349
372
|
}
|
|
373
|
+
/** Words a section's name adds to a group's without naming another: "Family portrait gallery" is "Family portraits". */ const GROUP_FRAMING = new Set('a an and the of our my your for with in at to by from all more recent selected featured favorite gallery galleries portfolio collection collections work works photos photo photographs pictures images showcase grid list cards section'.split(' '));
|
|
374
|
+
/** A name's words as a group is matched by: lowercase, a plural's "-s" dropped, framing kept. */ function groupWords(name) {
|
|
375
|
+
return name.toLowerCase().split(/[^a-z0-9À-ɏ]+/).filter(Boolean).map((word)=>word.length > 3 && word.endsWith('ies') ? `${word.slice(0, -3)}y` : word.length > 3 && /[^s]s$/.test(word) ? word.slice(0, -1) : word);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* The value of a dataset's groups a section's name is named for (AGL-3921),
|
|
379
|
+
* or `null`: every word of the value is in the name ("Weddings and
|
|
380
|
+
* elopements gallery" is "Weddings and elopements"), else every word of the
|
|
381
|
+
* name past its framing is in the value ("Weddings" is "Weddings and
|
|
382
|
+
* elopements"), and no other value is named as well. Pure.
|
|
383
|
+
*/ export function aiLayoutSectionGroup(sectionName, values) {
|
|
384
|
+
const said = groupWords(sectionName);
|
|
385
|
+
const own = said.filter((word)=>!GROUP_FRAMING.has(word));
|
|
386
|
+
const distinct = [
|
|
387
|
+
...new Map(values.filter((value)=>value.trim()).map((value)=>[
|
|
388
|
+
value.trim().toLowerCase(),
|
|
389
|
+
value.trim()
|
|
390
|
+
])).values()
|
|
391
|
+
];
|
|
392
|
+
const wordsOf = (value)=>groupWords(value).filter((word)=>!GROUP_FRAMING.has(word));
|
|
393
|
+
const found = distinct.filter((value)=>{
|
|
394
|
+
const words = wordsOf(value);
|
|
395
|
+
return words.length > 0 && words.every((word)=>said.includes(word)) || own.length > 0 && own.every((word)=>words.includes(word));
|
|
396
|
+
});
|
|
397
|
+
if (found.length !== 1) return null;
|
|
398
|
+
const value = found[0];
|
|
399
|
+
// "Weddings and family portraits" names a second group in part: it lists both.
|
|
400
|
+
const others = distinct.filter((other)=>other !== value).flatMap(wordsOf);
|
|
401
|
+
const mine = wordsOf(value);
|
|
402
|
+
return own.some((word)=>others.includes(word) && !mine.includes(word)) ? null : value;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* A page's placements of one dataset, each section named for one of its
|
|
406
|
+
* groups listing only that group's records (AGL-3921). The beta.265 Laurel
|
|
407
|
+
* Ridge "Galleries" page planned "Weddings and elopements gallery" and
|
|
408
|
+
* "Family portrait gallery" over one "Gallery collections" whose records were
|
|
409
|
+
* filed under those two categories, and both sections listed all ten: the
|
|
410
|
+
* plan names no filter, so the build reads it off the sections' names. Only
|
|
411
|
+
* where the page lists the dataset more than once; a section named for no
|
|
412
|
+
* one group lists them all. Pure.
|
|
413
|
+
*/ export function aiLayoutGroupedPlacements(placements, screen, dataset) {
|
|
414
|
+
const groups = dataset.groups;
|
|
415
|
+
if (placements.length < 2 || !groups || !FIELD_ID.test(groups.field)) return [
|
|
416
|
+
...placements
|
|
417
|
+
];
|
|
418
|
+
return placements.map((placement)=>{
|
|
419
|
+
var _ref, _dataset_records;
|
|
420
|
+
var _screen_sections_placement_section;
|
|
421
|
+
const value = aiLayoutSectionGroup((_ref = (_screen_sections_placement_section = screen.sections[placement.section]) == null ? void 0 : _screen_sections_placement_section.name) != null ? _ref : '', groups.values);
|
|
422
|
+
if (!value) return placement;
|
|
423
|
+
const records = ((_dataset_records = dataset.records) != null ? _dataset_records : []).filter((_name, index)=>{
|
|
424
|
+
var _groups_values_index;
|
|
425
|
+
return ((_groups_values_index = groups.values[index]) != null ? _groups_values_index : '').trim().toLowerCase() === value.toLowerCase();
|
|
426
|
+
});
|
|
427
|
+
return _extends({}, placement, {
|
|
428
|
+
filter: {
|
|
429
|
+
field: groups.field,
|
|
430
|
+
value,
|
|
431
|
+
records
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
}
|
|
350
436
|
/**
|
|
351
437
|
* The sections that list the site's events (AGL-3755): every section whose
|
|
352
438
|
* plan names one of them in its `uses` (`new:<its name>`), once however many
|
|
@@ -370,5 +456,17 @@ const isHome = (slug)=>firstSegment(slug) === '';
|
|
|
370
456
|
}
|
|
371
457
|
return placements;
|
|
372
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* A field whose name says it holds what a record's photo shows (AGL-3856):
|
|
461
|
+
* the alt text of the record's Image, never words of its card.
|
|
462
|
+
*/ const RECORD_IMAGE_ALT = /^(?:image|photo|picture) (?:description|alt(?: text)?)$/i;
|
|
463
|
+
/** Whether a field's name says it holds what a record's photo shows (AGL-3856). */ export function aiLayoutIsRecordImageAlt(name) {
|
|
464
|
+
return RECORD_IMAGE_ALT.test(name.trim());
|
|
465
|
+
}
|
|
466
|
+
/** The field holding what a record's photo shows, by id, or `null` (AGL-3856). */ export function aiLayoutRecordImageAltField(fields) {
|
|
467
|
+
var _ref;
|
|
468
|
+
var _fields_find;
|
|
469
|
+
return (_ref = (_fields_find = fields.find((field)=>aiLayoutIsRecordImageAlt(field.name))) == null ? void 0 : _fields_find.id) != null ? _ref : null;
|
|
470
|
+
}
|
|
373
471
|
|
|
374
472
|
//# sourceMappingURL=ai-layout-listings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-listings.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiStorefrontSays } from './ai-layout-storefront'\n\n/**\n * A SITE'S OWN RECORDS, PLACED BY THE PLATFORM (AGL-3676).\n *\n * A guided start writes a store's first products and a blog's first posts,\n * then built the pages around them in the layout language — and the pages\n * showed neither. \"Featured candles\" and \"The candle range\" on the live\n * Ember & Wick start (2026-10-08) were cards the model wrote, a name and a\n * line each: no photo, no price, no link, nothing a shopper could open; the\n * Clay Notes blog's \"Featured writing\" was three cards that linked to no\n * post. The real records were on the site, one click away from nowhere.\n *\n * A LISTING is one kind of record a site keeps — its catalog, its blog —\n * placed by code in the sections whose plan says they show it, through the\n * element the plugin that keeps the records renders them with, bound to the\n * records themselves rather than to words about them. It is the form binding\n * one step on (a section whose plan `uses` a form places that form by its\n * id, whatever the answer remembered): the model writes the section's\n * heading and words, the platform places the records.\n *\n * - `products` — the commerce plugin's Product grid (`product-grid`), over\n * the site's whole active catalog: a photo, the name, the price (or\n * \"Price coming soon\" where the owner cleared it), each card linking its\n * product's page.\n * - `posts` — the content plugin's Collection Entries (`collectionEntries`)\n * over the blog's collection, its card a cover, the date and byline, the\n * title and the excerpt, linking each post.\n * - `tracks` — the music plugin's Music player (`musicPlayer`, AGL-3716) on a\n * music site, placed EMPTY: its \"add your tracks\" state, for the owner to\n * fill from their own media library. No job ever sources a recording, so\n * a tracks listing has no records and the player no source.\n * - `reviews` — a selling store's own customer reviews, through the commerce\n * plugin's Product reviews in its store-wide scope (`product-reviews`,\n * AGL-3676): approved reviews across the catalog with their star ratings.\n * A store that opens has none, and the element then shows nothing at all\n * on the published page, so no review is ever written for a shopper who\n * did not leave one (the FTC's fake-review rule; rule 14's \"never invent\n * a customer\").\n * - `signup` — a selling store's newsletter sign-up, through the commerce\n * plugin's Newsletter signup (`newsletter-signup`, AGL-3676): a consented\n * email field into the store's contacts, where a section says newsletter.\n * - `records` — one of the site's datasets (AGL-3616): a menu, a team, its\n * services, built by the site start and kept by the data plugin. Drawn as\n * a Box that repeats over the dataset (`repeatDataset`, the platform's own\n * repeat), its card binding the record's fields as `{{item.<field>}}`, in\n * the sections whose plan names the dataset in its `uses`. A site may list\n * several, one listing a dataset.\n * - `events` — the site's dated events (AGL-3755), which a guided start of\n * an event-led business drafts through the events-calendar plugin, listed\n * by its Event List (`eventList`) in the section whose plan names them in\n * its `uses`: each published event with its date, place and cover, and\n * its search-engine Event markup.\n *\n * The elements are named by their persisted ids, as the form binding names\n * `form`: a plugin never imports another's internals, and\n * `ai-layout-listings.spec.ts` holds the ids and props named here to the\n * source of the plugins that declare them.\n *\n * Model only: shapes and the inputs reader; the compiler draws them.\n */\n\nexport type AiLayoutListingKind = 'products' | 'posts' | 'tracks' | 'reviews' | 'signup' | 'records' | 'events'\n\n/** How a section shows a listing: a few of its records on a page about something else, or all of them on its own page. */\nexport type AiLayoutListingRole = 'featured' | 'index'\n\n/** One section of one page that places a listing. */\nexport interface AiLayoutListingPlacement {\n /** The page, by its plan id. */\n screenId: string\n /** The section, by plan index. */\n section: number\n role: AiLayoutListingRole\n}\n\n/** One field of a dataset a `records` listing shows, as its card binds it. */\nexport interface AiLayoutListingField {\n /** The field's id, which `{{item.<id>}}` reads. */\n id: string\n name: string\n /** The data plugin's stored type: `text`, `float`, `int32`, `bool`, `sorted`. */\n type: string\n}\n\n/** A kind of record the site keeps, and where its pages place it. */\nexport interface AiLayoutListing {\n /** `listing:products`, `listing:posts`: what a section's placement names. */\n id: string\n kind: AiLayoutListingKind\n /** What a visitor would call the whole list: \"the shop\", \"the blog\". */\n name: string\n /** The records it shows today, by name, so a page's words can name them. */\n records: string[]\n /** Where the whole list lives, as a path on the site: the blog's `/blog`. */\n href?: string\n /** The content collection a posts listing repeats, by its slug. */\n collectionSlug?: string\n /** The dataset a records listing repeats over, by id (AGL-3616). */\n datasetId?: string\n /** The dataset's fields, in its order, which the card binds. */\n fields?: AiLayoutListingField[]\n /**\n * The field holding each record's photo (AGL-3616), which the card leads\n * with as its Image's `src`, so a portfolio stays a gallery. Never among\n * `fields`: it is the card's picture, not its words.\n */\n imageField?: string\n /**\n * Where each record's own page lives (AGL-3727): the base path of the\n * dataset's record template (AGL-3475), without slashes at either end —\n * `services` for `/services/roofing`. Set by code where the plan makes or\n * the site keeps a record template for the dataset, never by the model.\n */\n recordBase?: string\n /**\n * The record's page-address field (AGL-3727), which the record's page is\n * served at under {@link recordBase}. Never among `fields`: it is the card's\n * link, not its words.\n */\n addressField?: string\n /**\n * Where a visitor goes from a store with nothing listed yet (AGL-3676): the\n * site's contact page, by its path. The empty state names it as its one\n * action, so a shop that opens empty still answers a visitor.\n */\n emptyAction?: { label: string; href: string }\n /**\n * `false` for a store that lists its catalog but cannot sell yet (its first\n * products were skipped or failed, AGL-3676): the header carries no cart.\n */\n cart?: boolean\n placements: AiLayoutListingPlacement[]\n}\n\n/** The unit input a site's listings travel in, to its layout and its pages. */\nexport const AI_LAYOUT_LISTINGS_INPUT = 'siteListings'\n\n/** The id a listing of a kind is placed by; a records listing is one dataset's. */\nexport function aiLayoutListingId(kind: AiLayoutListingKind, datasetId?: string): string {\n return kind === 'records' && datasetId ? `listing:records:${datasetId}` : `listing:${kind}`\n}\n\n/** The persisted element a listing is drawn with, by kind (held to its plugin's source by a spec). */\nexport const AI_LAYOUT_LISTING_ELEMENTS: Readonly<Record<AiLayoutListingKind, string>> = {\n products: 'product-grid',\n posts: 'collectionEntries',\n tracks: 'musicPlayer',\n reviews: 'product-reviews',\n signup: 'newsletter-signup',\n // The platform's own Box, repeating its card over the dataset.\n records: 'muiBox',\n events: 'eventList',\n}\n\n/** The header's cart button: the commerce plugin's Cart in its `button` variant. */\nexport const AI_LAYOUT_CART_ELEMENT = 'cart'\n\n/** The most records a featured band shows; a page of its own shows them all. */\nexport const AI_LAYOUT_FEATURED_RECORDS = { products: 4, posts: 3, tracks: 0, reviews: 3, signup: 0, records: 3, events: 3 } as const\n\nconst PLACEMENT_ROLES: readonly AiLayoutListingRole[] = ['featured', 'index']\nconst KINDS: readonly AiLayoutListingKind[] = ['products', 'posts', 'tracks', 'reviews', 'signup', 'records', 'events']\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What a visitor calls each kind's whole list, where a unit's input names none. */\nconst LISTING_NAMES: Readonly<Record<AiLayoutListingKind, string>> = {\n products: 'the shop',\n posts: 'the blog',\n tracks: 'the music',\n reviews: 'the store',\n signup: 'the newsletter',\n records: 'the list',\n events: 'the events',\n}\n\n/** A path on the site, as a listing's links are kept. */\nconst SITE_PATH = /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/\n\n/** A dataset id and a field id, as the data plugin mints them. */\nconst DATASET_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/\n\n/** A record template's base: one to four URL-safe segments, as the data plugin keeps it. */\nconst RECORD_BASE = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\\/[a-z0-9]+(?:-[a-z0-9]+)*){0,3}$/\n\n/**\n * The link each card of a records listing opens (AGL-3727): the record's own\n * page, `/{base}/{{item.<address field>}}`, which the page fills per record\n * as it repeats the card. `null` where the dataset has no record page, or the\n * listing names no address to reach it by.\n */\nexport function aiLayoutRecordHref(listing: Pick<AiLayoutListing, 'kind' | 'recordBase' | 'addressField'>): string | null {\n if (listing.kind !== 'records') return null\n const base = text(listing.recordBase).replace(/^\\/+|\\/+$/g, '')\n const field = text(listing.addressField)\n return RECORD_BASE.test(base) && FIELD_ID.test(field) ? `/${base}/{{item.${field}}}` : null\n}\n\n/** A records listing's fields as its input carries them; nothing that does not read is kept. */\nfunction listingFieldsOf(raw: unknown): AiLayoutListingField[] {\n if (!Array.isArray(raw)) return []\n return raw\n .flatMap((value) => {\n const field = (value ?? {}) as Record<string, unknown>\n const id = text(field['id'])\n return FIELD_ID.test(id) ? [{ id, name: text(field['name']).slice(0, 60) || id, type: text(field['type']) || 'text' }] : []\n })\n .slice(0, 16)\n}\n\n/** A site's listings as its unit inputs carry them; nothing that does not read is kept. */\nexport function aiLayoutListingsOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutListing[] {\n const raw = inputs?.[AI_LAYOUT_LISTINGS_INPUT]\n if (!Array.isArray(raw)) return []\n const listings: AiLayoutListing[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const record = entry as Record<string, unknown>\n const kind = record['kind'] as AiLayoutListingKind\n // A records listing is one dataset's (AGL-3616): a site may list several.\n const datasetId = kind === 'records' ? text(record['datasetId']) : ''\n const fields = kind === 'records' ? listingFieldsOf(record['fields']) : []\n const imageField = kind === 'records' ? text(record['imageField']) : ''\n const recordBase = kind === 'records' ? text(record['recordBase']).replace(/^\\/+|\\/+$/g, '') : ''\n const addressField = kind === 'records' ? text(record['addressField']) : ''\n // Its fields may be left for the page to read off the site's inventory.\n if (kind === 'records' && !DATASET_ID.test(datasetId)) continue\n if (\n !KINDS.includes(kind) ||\n listings.some((listing) => listing.kind === kind && (kind !== 'records' || listing.datasetId === datasetId))\n ) {\n continue\n }\n const href = text(record['href'])\n const action = (record['emptyAction'] ?? {}) as Record<string, unknown>\n const actionLabel = text(action['label']).slice(0, 40)\n const actionHref = text(action['href'])\n const collectionSlug = text(record['collectionSlug'])\n // A posts listing with no collection to repeat shows nothing.\n if (kind === 'posts' && !/^[a-z0-9-]{1,100}$/.test(collectionSlug)) continue\n const placements = (Array.isArray(record['placements']) ? record['placements'] : []).flatMap((value) => {\n const placement = (value ?? {}) as Record<string, unknown>\n const screenId = text(placement['screenId'])\n const section = placement['section']\n const role = placement['role'] as AiLayoutListingRole\n return screenId && typeof section === 'number' && Number.isInteger(section) && section >= 0 && PLACEMENT_ROLES.includes(role)\n ? [{ screenId, section, role }]\n : []\n })\n listings.push({\n id: aiLayoutListingId(kind, datasetId),\n kind,\n name: text(record['name']) || LISTING_NAMES[kind],\n // A tracks listing names no records: no job sources a recording (AGL-3716);\n // nor do a store's reviews and its sign-up, which shoppers fill (AGL-3676).\n records:\n kind !== 'products' && kind !== 'posts' && kind !== 'records' && kind !== 'events'\n ? []\n : (Array.isArray(record['records']) ? record['records'] : []).map(text).filter(Boolean).slice(0, 12),\n ...(SITE_PATH.test(href) ? { href } : {}),\n ...(kind === 'posts' ? { collectionSlug } : {}),\n ...(kind === 'records' ? { datasetId, fields } : {}),\n ...(kind === 'records' && FIELD_ID.test(imageField) ? { imageField } : {}),\n // A record page's base, and the address field where the unit knew it; a\n // build's page reads the field off the site's inventory (AGL-3727).\n ...(kind === 'records' && RECORD_BASE.test(recordBase) ? { recordBase } : {}),\n ...(kind === 'records' && RECORD_BASE.test(recordBase) && FIELD_ID.test(addressField) ? { addressField } : {}),\n ...(kind === 'products' && record['cart'] === false ? { cart: false } : {}),\n ...(kind === 'products' && actionLabel && SITE_PATH.test(actionHref) ? { emptyAction: { label: actionLabel, href: actionHref } } : {}),\n placements,\n })\n }\n return listings\n}\n\n/** The listing a section of a page places, and how; `null` for a section that places none. */\nexport function aiLayoutListingAt(\n listings: readonly AiLayoutListing[],\n screenId: string | null | undefined,\n section: number,\n): { listing: AiLayoutListing; role: AiLayoutListingRole } | null {\n if (!screenId) return null\n for (const listing of listings) {\n const placement = listing.placements.find((entry) => entry.screenId === screenId && entry.section === section)\n if (placement) return { listing, role: placement.role }\n }\n return null\n}\n\n// ── Where a site's pages place them ────────────────────────────────────\n\n/** The plan of a page, as a placement is chosen from it. */\nexport interface AiLayoutListingScreen {\n id?: string | null\n title: string\n slug: string\n sections: ReadonlyArray<{ name: string; items: number; uses?: readonly string[] }>\n /** The dataset this page is the record template of, and its base (AGL-3475); absent for a page that is one page. */\n record?: { dataset: string; base: string } | null\n}\n\nconst PRODUCT_PAGE = /\\b(shop|store|products?|catalog(?:ue)?|collections?|range|browse)\\b/i\nconst PRODUCT_SECTION =\n /\\b(shop|store|products?|catalog(?:ue)?|collections?|range|bestsellers?|best[- ]sellers?|new arrivals|arrivals|featured|favou?rites|signature)\\b/i\nconst POST_SECTION = /\\b(blog|posts?|articles?|writing|journal|stories|essays?|latest|recent|news|featured)\\b/i\n/** A music site's page or section about its recordings (AGL-3716). */\nconst MUSIC_PAGE = /\\b(music|listen|tracks?|songs?|releases?|discography|albums?|eps?|singles?|sounds?)\\b/i\n\n/**\n * The section of a shop's own page the whole catalog fills (AGL-3676): the\n * first after its opening whose name says products (\"Product grid\", \"The\n * range\"), else the section planned with the most items, else its second.\n * The live Hearth & Wick start (2026-10-09) planned \"Product range image\n * cards\" — six cards naming kinds of candle, with no product, price or cart.\n */\nexport function aiLayoutShopGridSection(sections: ReadonlyArray<{ name: string; items: number }>): number {\n const named = sections.findIndex((section, index) => index > 0 && PRODUCT_SECTION.test(section.name) && !SHOP_FRAMING.test(section.name))\n if (named !== -1) return named\n const most = Math.max(0, ...sections.map((section) => section.items))\n const byItems = most > 0 ? sections.findIndex((section) => section.items === most) : -1\n return byItems !== -1 ? byItems : sections.length > 1 ? 1 : 0\n}\n\n/**\n * The section of a store's home its bestsellers fill (AGL-3676): the first\n * after the hero whose name says them (\"Bestsellers\", \"Featured candles\", \"New\n * arrivals\"), never one about the store's collections, its reviews or its\n * sign-up; else the first after the hero naming products at all; else the\n * first after the hero planned with items that is none of those. The\n * beta.237 Willow Wick home would otherwise have listed products in its\n * \"Shop by collection\" tiles.\n */\nfunction aiLayoutFeaturedSection(screen: AiLayoutListingScreen): number {\n // A section about the collections that does not also name the bestsellers.\n const other = (name: string) =>\n (aiStorefrontSays('collections', name) && !aiStorefrontSays('featured', name)) ||\n aiStorefrontSays('reviews', name) ||\n aiStorefrontSays('signup', name)\n const first = (test: (name: string, items: number) => boolean) =>\n screen.sections.findIndex((section, index) => index > 0 && test(section.name, section.items))\n for (const test of [\n (name: string) => aiStorefrontSays('featured', name) && !other(name),\n (name: string) => PRODUCT_SECTION.test(name) && !other(name),\n (name: string, items: number) => items > 0 && !other(name),\n (name: string) => PRODUCT_SECTION.test(name),\n ]) {\n const found = first(test)\n if (found !== -1) return found\n }\n return -1\n}\n\n/** A section of a shop page that only opens or closes it: \"Shop intro heading\", \"Gift help call to action\". */\nconst SHOP_FRAMING = /\\b(hero|intro(?:duction)?|heading|banner|cta|call to action|contact)\\b/i\n\n/** Whether a planned page is the store's own Shop page: its address or its name says it sells. */\nexport function aiLayoutIsShopPage(screen: { slug: string; title: string }): boolean {\n return !isHome(screen.slug) && PRODUCT_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`)\n}\n\nconst firstSegment = (slug: string) => slug.trim().replace(/^\\/+/, '').split('/')[0].toLowerCase()\nconst isHome = (slug: string) => firstSegment(slug) === ''\n\n/**\n * The sections of a site's pages that show a listing, chosen by code from the\n * plan (AGL-3676), never asked of the model:\n *\n * - PRODUCTS. The shop's own page — the page whose address or name says it\n * sells (`/shop`, \"Shop\", \"Products\") — shows the whole catalog in the\n * section the plan gave the most items, else its second; and the home\n * page features a few in its first section after the hero whose name\n * says products (\"Featured candles\", \"Bestsellers\"), else in the first\n * section after the hero planned with items.\n * - POSTS. The home features the latest in its first section of items after\n * the hero whose name says writing (\"Featured writing\", \"Latest posts\"),\n * else its first section of items after the hero; and any other page\n * whose name says it is about the writing does the same. The blog's own\n * index is the platform's.\n *\n * A section placed once is placed for one listing.\n */\nexport function aiLayoutListingPlacements(\n kind: AiLayoutListingKind,\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const placements: AiLayoutListingPlacement[] = []\n const place = (screen: AiLayoutListingScreen, section: number, role: AiLayoutListingRole) => {\n if (typeof screen.id === 'string' && screen.id && section >= 0) placements.push({ screenId: screen.id, section, role })\n }\n const afterHero = (screen: AiLayoutListingScreen, test: (name: string, items: number) => boolean) =>\n screen.sections.findIndex((section, index) => index > 0 && test(section.name, section.items))\n if (kind === 'tracks') {\n // A music site's player (AGL-3716): on its Music page, in the section that\n // says it is about the recordings, else the one after the opening; on the\n // home, only in a section named for them. A site with neither gets it on\n // its home, after the opening.\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n place(screen, afterHero(screen, (name) => MUSIC_PAGE.test(name)), 'featured')\n continue\n }\n if (!MUSIC_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`)) continue\n const named = afterHero(screen, (name) => MUSIC_PAGE.test(name))\n place(screen, named !== -1 ? named : screen.sections.length > 1 ? 1 : 0, 'index')\n }\n if (!placements.length) {\n const home = screens.find((screen) => isHome(screen.slug))\n if (home) place(home, home.sections.length > 1 ? 1 : 0, 'featured')\n }\n return placements\n }\n if (kind === 'reviews' || kind === 'signup') {\n // A store's own reviews, on its home, in the section named for them; its\n // newsletter sign-up in any page's section named for it that places no\n // form of the plan's (AGL-3676). Neither is ever a page's opening.\n for (const screen of screens) {\n if (kind === 'reviews' && !isHome(screen.slug)) continue\n place(\n screen,\n afterHero(\n screen,\n (name) => aiStorefrontSays(kind, name),\n ),\n kind === 'reviews' ? 'featured' : 'index',\n )\n }\n return kind === 'signup'\n ? placements.filter((placement) => {\n const screen = screens.find((entry) => entry.id === placement.screenId)\n return !(screen?.sections[placement.section]?.uses ?? []).some((use) => /form/i.test(use))\n })\n : placements\n }\n if (kind === 'products') {\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n place(screen, aiLayoutFeaturedSection(screen), 'featured')\n continue\n }\n if (!aiLayoutIsShopPage(screen)) continue\n place(screen, aiLayoutShopGridSection(screen.sections), 'index')\n }\n return placements\n }\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n // On a blog's home the band that names the writing (\"Featured posts\n // cards\", \"Featured journeys\") lists it; failing one, the first band of\n // items after the hero. A live Clay Notes plan (2026-10-09) opened its\n // home with \"What you will find\" before \"Featured posts cards\".\n const named = afterHero(screen, (name, items) => items > 0 && POST_SECTION.test(name))\n place(screen, named !== -1 ? named : afterHero(screen, (_name, items) => items > 0), 'featured')\n continue\n }\n if (!POST_SECTION.test(screen.title)) continue\n place(screen, afterHero(screen, (name) => POST_SECTION.test(name)), 'featured')\n }\n return placements\n}\n\n/**\n * Where a dataset's records have pages of their own (AGL-3727): the base of\n * the page the plan makes its record template (AGL-3475), named by its id or\n * as `new:<its name>`; `null` where no page is. Read off the plan, never\n * asked of the model, so every card that lists the dataset links its record.\n */\nexport function aiLayoutRecordBaseOf(\n dataset: { id: string; name: string },\n screens: readonly AiLayoutListingScreen[],\n): string | null {\n const named = `new:${dataset.name}`.trim().toLowerCase()\n for (const screen of screens) {\n const ref = text(screen.record?.dataset)\n if (!ref || (ref !== dataset.id && ref.toLowerCase() !== named)) continue\n const base = text(screen.record?.base).replace(/^\\/+|\\/+$/g, '')\n if (RECORD_BASE.test(base)) return base\n }\n return null\n}\n\n/**\n * The sections that list one of the site's datasets (AGL-3616): every section\n * whose plan names it in its `uses` — `new:<its name>` as the plan wrote it,\n * or its id once built. The home page features a few of its records; any\n * other page lists them all. Chosen from the plan, never asked of the model.\n */\nexport function aiLayoutRecordsPlacements(\n dataset: { id: string; name: string },\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const named = `new:${dataset.name}`.trim().toLowerCase()\n const placements: AiLayoutListingPlacement[] = []\n for (const screen of screens) {\n if (typeof screen.id !== 'string' || !screen.id) continue\n screen.sections.forEach((section, index) => {\n const lists = (section.uses ?? []).some((ref) => ref === dataset.id || ref.trim().toLowerCase() === named)\n if (lists) placements.push({ screenId: screen.id as string, section: index, role: isHome(screen.slug) ? 'featured' : 'index' })\n })\n }\n return placements\n}\n\n/**\n * The sections that list the site's events (AGL-3755): every section whose\n * plan names one of them in its `uses` (`new:<its name>`), once however many\n * it names. The home page features the next few; any other page lists them\n * all. Chosen from the plan, never asked of the model.\n */\nexport function aiLayoutEventsPlacements(\n names: readonly string[],\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const named = new Set(names.map((name) => `new:${name}`.trim().toLowerCase()))\n const placements: AiLayoutListingPlacement[] = []\n for (const screen of screens) {\n if (typeof screen.id !== 'string' || !screen.id) continue\n screen.sections.forEach((section, index) => {\n if ((section.uses ?? []).some((ref) => named.has(ref.trim().toLowerCase()))) {\n placements.push({ screenId: screen.id as string, section: index, role: isHome(screen.slug) ? 'featured' : 'index' })\n }\n })\n }\n return placements\n}\n"],"names":["aiStorefrontSays","AI_LAYOUT_LISTINGS_INPUT","aiLayoutListingId","kind","datasetId","AI_LAYOUT_LISTING_ELEMENTS","products","posts","tracks","reviews","signup","records","events","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_FEATURED_RECORDS","PLACEMENT_ROLES","KINDS","text","value","trim","LISTING_NAMES","SITE_PATH","DATASET_ID","FIELD_ID","RECORD_BASE","aiLayoutRecordHref","listing","base","recordBase","replace","field","addressField","test","listingFieldsOf","raw","Array","isArray","flatMap","id","name","slice","type","aiLayoutListingsOf","inputs","listings","entry","record","fields","imageField","includes","some","href","action","actionLabel","actionHref","collectionSlug","placements","placement","screenId","section","role","Number","isInteger","push","map","filter","Boolean","cart","emptyAction","label","aiLayoutListingAt","find","PRODUCT_PAGE","PRODUCT_SECTION","POST_SECTION","MUSIC_PAGE","aiLayoutShopGridSection","sections","named","findIndex","index","SHOP_FRAMING","most","Math","max","items","byItems","length","aiLayoutFeaturedSection","screen","other","first","found","aiLayoutIsShopPage","isHome","slug","firstSegment","title","split","toLowerCase","aiLayoutListingPlacements","screens","place","afterHero","home","uses","use","_name","aiLayoutRecordBaseOf","dataset","ref","aiLayoutRecordsPlacements","forEach","lists","aiLayoutEventsPlacements","names","Set","has"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-listings.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiStorefrontSays } from './ai-layout-storefront'\n\n/**\n * A SITE'S OWN RECORDS, PLACED BY THE PLATFORM (AGL-3676).\n *\n * A guided start writes a store's first products and a blog's first posts,\n * then built the pages around them in the layout language — and the pages\n * showed neither. \"Featured candles\" and \"The candle range\" on the live\n * Ember & Wick start (2026-10-08) were cards the model wrote, a name and a\n * line each: no photo, no price, no link, nothing a shopper could open; the\n * Clay Notes blog's \"Featured writing\" was three cards that linked to no\n * post. The real records were on the site, one click away from nowhere.\n *\n * A LISTING is one kind of record a site keeps — its catalog, its blog —\n * placed by code in the sections whose plan says they show it, through the\n * element the plugin that keeps the records renders them with, bound to the\n * records themselves rather than to words about them. It is the form binding\n * one step on (a section whose plan `uses` a form places that form by its\n * id, whatever the answer remembered): the model writes the section's\n * heading and words, the platform places the records.\n *\n * - `products` — the commerce plugin's Product grid (`product-grid`), over\n * the site's whole active catalog: a photo, the name, the price (or\n * \"Price coming soon\" where the owner cleared it), each card linking its\n * product's page.\n * - `posts` — the content plugin's Collection Entries (`collectionEntries`)\n * over the blog's collection, its card a cover, the date and byline, the\n * title and the excerpt, linking each post.\n * - `tracks` — the music plugin's Music player (`musicPlayer`, AGL-3716) on a\n * music site, placed EMPTY: its \"add your tracks\" state, for the owner to\n * fill from their own media library. No job ever sources a recording, so\n * a tracks listing has no records and the player no source.\n * - `reviews` — a selling store's own customer reviews, through the commerce\n * plugin's Product reviews in its store-wide scope (`product-reviews`,\n * AGL-3676): approved reviews across the catalog with their star ratings.\n * A store that opens has none, and the element then shows nothing at all\n * on the published page, so no review is ever written for a shopper who\n * did not leave one (the FTC's fake-review rule; rule 14's \"never invent\n * a customer\").\n * - `signup` — a selling store's newsletter sign-up, through the commerce\n * plugin's Newsletter signup (`newsletter-signup`, AGL-3676): a consented\n * email field into the store's contacts, where a section says newsletter.\n * - `records` — one of the site's datasets (AGL-3616): a menu, a team, its\n * services, built by the site start and kept by the data plugin. Drawn as\n * a Box that repeats over the dataset (`repeatDataset`, the platform's own\n * repeat), its card binding the record's fields as `{{item.<field>}}`, in\n * the sections whose plan names the dataset in its `uses`. A site may list\n * several, one listing a dataset.\n * - `events` — the site's dated events (AGL-3755), which a guided start of\n * an event-led business drafts through the events-calendar plugin, listed\n * by its Event List (`eventList`) in the section whose plan names them in\n * its `uses`: each published event with its date, place and cover, and\n * its search-engine Event markup.\n *\n * The elements are named by their persisted ids, as the form binding names\n * `form`: a plugin never imports another's internals, and\n * `ai-layout-listings.spec.ts` holds the ids and props named here to the\n * source of the plugins that declare them.\n *\n * Model only: shapes and the inputs reader; the compiler draws them.\n */\n\nexport type AiLayoutListingKind = 'products' | 'posts' | 'tracks' | 'reviews' | 'signup' | 'records' | 'events'\n\n/** How a section shows a listing: a few of its records on a page about something else, or all of them on its own page. */\nexport type AiLayoutListingRole = 'featured' | 'index'\n\n/** One section of one page that places a listing. */\nexport interface AiLayoutListingPlacement {\n /** The page, by its plan id. */\n screenId: string\n /** The section, by plan index. */\n section: number\n role: AiLayoutListingRole\n /**\n * The one group of a dataset's records the section lists (AGL-3921), where\n * the page lists the dataset in several sections each named for one value\n * of its category-like field: \"Family portrait gallery\" lists the records\n * filed \"Family portraits\", drawn as the repeat's `repeatFilter`.\n */\n filter?: AiLayoutListingFilter\n}\n\n/** A records placement's group: the field, its value, and the records filed under it, by name. */\nexport interface AiLayoutListingFilter {\n field: string\n value: string\n records: string[]\n}\n\n/** One field of a dataset a `records` listing shows, as its card binds it. */\nexport interface AiLayoutListingField {\n /** The field's id, which `{{item.<id>}}` reads. */\n id: string\n name: string\n /** The data plugin's stored type: `text`, `float`, `int32`, `bool`, `sorted`. */\n type: string\n}\n\n/** A kind of record the site keeps, and where its pages place it. */\nexport interface AiLayoutListing {\n /** `listing:products`, `listing:posts`: what a section's placement names. */\n id: string\n kind: AiLayoutListingKind\n /** What a visitor would call the whole list: \"the shop\", \"the blog\". */\n name: string\n /** The records it shows today, by name, so a page's words can name them. */\n records: string[]\n /** Where the whole list lives, as a path on the site: the blog's `/blog`. */\n href?: string\n /** The content collection a posts listing repeats, by its slug. */\n collectionSlug?: string\n /** The dataset a records listing repeats over, by id (AGL-3616). */\n datasetId?: string\n /** The dataset's fields, in its order, which the card binds. */\n fields?: AiLayoutListingField[]\n /**\n * The field holding each record's photo (AGL-3616), which the card leads\n * with as its Image's `src`, so a portfolio stays a gallery. Never among\n * `fields`: it is the card's picture, not its words.\n */\n imageField?: string\n /**\n * Where each record's own page lives (AGL-3727): the base path of the\n * dataset's record template (AGL-3475), without slashes at either end —\n * `services` for `/services/roofing`. Set by code where the plan makes or\n * the site keeps a record template for the dataset, never by the model.\n */\n recordBase?: string\n /**\n * The record's page-address field (AGL-3727), which the record's page is\n * served at under {@link recordBase}. Never among `fields`: it is the card's\n * link, not its words.\n */\n addressField?: string\n /**\n * Where a visitor goes from a store with nothing listed yet (AGL-3676): the\n * site's contact page, by its path. The empty state names it as its one\n * action, so a shop that opens empty still answers a visitor.\n */\n emptyAction?: { label: string; href: string }\n /**\n * `false` for a store that lists its catalog but cannot sell yet (its first\n * products were skipped or failed, AGL-3676): the header carries no cart.\n */\n cart?: boolean\n placements: AiLayoutListingPlacement[]\n}\n\n/** The unit input a site's listings travel in, to its layout and its pages. */\nexport const AI_LAYOUT_LISTINGS_INPUT = 'siteListings'\n\n/** The id a listing of a kind is placed by; a records listing is one dataset's. */\nexport function aiLayoutListingId(kind: AiLayoutListingKind, datasetId?: string): string {\n return kind === 'records' && datasetId ? `listing:records:${datasetId}` : `listing:${kind}`\n}\n\n/** The persisted element a listing is drawn with, by kind (held to its plugin's source by a spec). */\nexport const AI_LAYOUT_LISTING_ELEMENTS: Readonly<Record<AiLayoutListingKind, string>> = {\n products: 'product-grid',\n posts: 'collectionEntries',\n tracks: 'musicPlayer',\n reviews: 'product-reviews',\n signup: 'newsletter-signup',\n // The platform's own Box, repeating its card over the dataset.\n records: 'muiBox',\n events: 'eventList',\n}\n\n/** The header's cart button: the commerce plugin's Cart in its `button` variant. */\nexport const AI_LAYOUT_CART_ELEMENT = 'cart'\n\n/** The most records a featured band shows; a page of its own shows them all. */\nexport const AI_LAYOUT_FEATURED_RECORDS = { products: 4, posts: 3, tracks: 0, reviews: 3, signup: 0, records: 3, events: 3 } as const\n\nconst PLACEMENT_ROLES: readonly AiLayoutListingRole[] = ['featured', 'index']\nconst KINDS: readonly AiLayoutListingKind[] = ['products', 'posts', 'tracks', 'reviews', 'signup', 'records', 'events']\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What a visitor calls each kind's whole list, where a unit's input names none. */\nconst LISTING_NAMES: Readonly<Record<AiLayoutListingKind, string>> = {\n products: 'the shop',\n posts: 'the blog',\n tracks: 'the music',\n reviews: 'the store',\n signup: 'the newsletter',\n records: 'the list',\n events: 'the events',\n}\n\n/** A path on the site, as a listing's links are kept. */\nconst SITE_PATH = /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/\n\n/** A dataset id and a field id, as the data plugin mints them. */\nconst DATASET_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/\n\n/** A record template's base: one to four URL-safe segments, as the data plugin keeps it. */\nconst RECORD_BASE = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\\/[a-z0-9]+(?:-[a-z0-9]+)*){0,3}$/\n\n/**\n * The link each card of a records listing opens (AGL-3727): the record's own\n * page, `/{base}/{{item.<address field>}}`, which the page fills per record\n * as it repeats the card. `null` where the dataset has no record page, or the\n * listing names no address to reach it by.\n */\nexport function aiLayoutRecordHref(listing: Pick<AiLayoutListing, 'kind' | 'recordBase' | 'addressField'>): string | null {\n if (listing.kind !== 'records') return null\n const base = text(listing.recordBase).replace(/^\\/+|\\/+$/g, '')\n const field = text(listing.addressField)\n return RECORD_BASE.test(base) && FIELD_ID.test(field) ? `/${base}/{{item.${field}}}` : null\n}\n\n/** A records listing's fields as its input carries them; nothing that does not read is kept. */\nfunction listingFieldsOf(raw: unknown): AiLayoutListingField[] {\n if (!Array.isArray(raw)) return []\n return raw\n .flatMap((value) => {\n const field = (value ?? {}) as Record<string, unknown>\n const id = text(field['id'])\n return FIELD_ID.test(id) ? [{ id, name: text(field['name']).slice(0, 60) || id, type: text(field['type']) || 'text' }] : []\n })\n .slice(0, 16)\n}\n\n/** A placement's group as its input carries it; one whose field or value does not read is none. */\nfunction listingFilterOf(raw: unknown): AiLayoutListingFilter | null {\n const filter = (raw ?? {}) as Record<string, unknown>\n const field = text(filter['field'])\n // The repeat's filter is `field == value` on one line, at most 200 characters.\n const value = text(filter['value']).replace(/\\s+/g, ' ').slice(0, 120)\n if (!FIELD_ID.test(field) || !value) return null\n const records = (Array.isArray(filter['records']) ? filter['records'] : []).map(text).filter(Boolean).slice(0, 12)\n return { field, value, records }\n}\n\n/** A site's listings as its unit inputs carry them; nothing that does not read is kept. */\nexport function aiLayoutListingsOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutListing[] {\n const raw = inputs?.[AI_LAYOUT_LISTINGS_INPUT]\n if (!Array.isArray(raw)) return []\n const listings: AiLayoutListing[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const record = entry as Record<string, unknown>\n const kind = record['kind'] as AiLayoutListingKind\n // A records listing is one dataset's (AGL-3616): a site may list several.\n const datasetId = kind === 'records' ? text(record['datasetId']) : ''\n const fields = kind === 'records' ? listingFieldsOf(record['fields']) : []\n const imageField = kind === 'records' ? text(record['imageField']) : ''\n const recordBase = kind === 'records' ? text(record['recordBase']).replace(/^\\/+|\\/+$/g, '') : ''\n const addressField = kind === 'records' ? text(record['addressField']) : ''\n // Its fields may be left for the page to read off the site's inventory.\n if (kind === 'records' && !DATASET_ID.test(datasetId)) continue\n if (\n !KINDS.includes(kind) ||\n listings.some((listing) => listing.kind === kind && (kind !== 'records' || listing.datasetId === datasetId))\n ) {\n continue\n }\n const href = text(record['href'])\n const action = (record['emptyAction'] ?? {}) as Record<string, unknown>\n const actionLabel = text(action['label']).slice(0, 40)\n const actionHref = text(action['href'])\n const collectionSlug = text(record['collectionSlug'])\n // A posts listing with no collection to repeat shows nothing.\n if (kind === 'posts' && !/^[a-z0-9-]{1,100}$/.test(collectionSlug)) continue\n const placements = (Array.isArray(record['placements']) ? record['placements'] : []).flatMap((value) => {\n const placement = (value ?? {}) as Record<string, unknown>\n const screenId = text(placement['screenId'])\n const section = placement['section']\n const role = placement['role'] as AiLayoutListingRole\n const filter = kind === 'records' ? listingFilterOf(placement['filter']) : null\n return screenId && typeof section === 'number' && Number.isInteger(section) && section >= 0 && PLACEMENT_ROLES.includes(role)\n ? [{ screenId, section, role, ...(filter ? { filter } : {}) }]\n : []\n })\n listings.push({\n id: aiLayoutListingId(kind, datasetId),\n kind,\n name: text(record['name']) || LISTING_NAMES[kind],\n // A tracks listing names no records: no job sources a recording (AGL-3716);\n // nor do a store's reviews and its sign-up, which shoppers fill (AGL-3676).\n records:\n kind !== 'products' && kind !== 'posts' && kind !== 'records' && kind !== 'events'\n ? []\n : (Array.isArray(record['records']) ? record['records'] : []).map(text).filter(Boolean).slice(0, 12),\n ...(SITE_PATH.test(href) ? { href } : {}),\n ...(kind === 'posts' ? { collectionSlug } : {}),\n ...(kind === 'records' ? { datasetId, fields } : {}),\n ...(kind === 'records' && FIELD_ID.test(imageField) ? { imageField } : {}),\n // A record page's base, and the address field where the unit knew it; a\n // build's page reads the field off the site's inventory (AGL-3727).\n ...(kind === 'records' && RECORD_BASE.test(recordBase) ? { recordBase } : {}),\n ...(kind === 'records' && RECORD_BASE.test(recordBase) && FIELD_ID.test(addressField) ? { addressField } : {}),\n ...(kind === 'products' && record['cart'] === false ? { cart: false } : {}),\n ...(kind === 'products' && actionLabel && SITE_PATH.test(actionHref) ? { emptyAction: { label: actionLabel, href: actionHref } } : {}),\n placements,\n })\n }\n return listings\n}\n\n/** The listing a section of a page places, and how; `null` for a section that places none. */\nexport function aiLayoutListingAt(\n listings: readonly AiLayoutListing[],\n screenId: string | null | undefined,\n section: number,\n): { listing: AiLayoutListing; role: AiLayoutListingRole; filter?: AiLayoutListingFilter } | null {\n if (!screenId) return null\n for (const listing of listings) {\n const placement = listing.placements.find((entry) => entry.screenId === screenId && entry.section === section)\n if (placement) return { listing, role: placement.role, ...(placement.filter ? { filter: placement.filter } : {}) }\n }\n return null\n}\n\n/** The repeat filter a placement's group is drawn as: `category == Family portraits`. */\nexport function aiLayoutRepeatFilterOf(filter: Pick<AiLayoutListingFilter, 'field' | 'value'>): string {\n return `${filter.field} == ${filter.value}`\n}\n\n// ── Where a site's pages place them ────────────────────────────────────\n\n/** The plan of a page, as a placement is chosen from it. */\nexport interface AiLayoutListingScreen {\n id?: string | null\n title: string\n slug: string\n sections: ReadonlyArray<{ name: string; items: number; uses?: readonly string[] }>\n /** The dataset this page is the record template of, and its base (AGL-3475); absent for a page that is one page. */\n record?: { dataset: string; base: string } | null\n}\n\nconst PRODUCT_PAGE = /\\b(shop|store|products?|catalog(?:ue)?|collections?|range|browse)\\b/i\nconst PRODUCT_SECTION =\n /\\b(shop|store|products?|catalog(?:ue)?|collections?|range|bestsellers?|best[- ]sellers?|new arrivals|arrivals|featured|favou?rites|signature)\\b/i\nconst POST_SECTION = /\\b(blog|posts?|articles?|writing|journal|stories|essays?|latest|recent|news|featured)\\b/i\n/** A music site's page or section about its recordings (AGL-3716). */\nconst MUSIC_PAGE = /\\b(music|listen|tracks?|songs?|releases?|discography|albums?|eps?|singles?|sounds?)\\b/i\n\n/**\n * The section of a shop's own page the whole catalog fills (AGL-3676): the\n * first after its opening whose name says products (\"Product grid\", \"The\n * range\"), else the section planned with the most items, else its second.\n * The live Hearth & Wick start (2026-10-09) planned \"Product range image\n * cards\" — six cards naming kinds of candle, with no product, price or cart.\n */\nexport function aiLayoutShopGridSection(sections: ReadonlyArray<{ name: string; items: number }>): number {\n const named = sections.findIndex((section, index) => index > 0 && PRODUCT_SECTION.test(section.name) && !SHOP_FRAMING.test(section.name))\n if (named !== -1) return named\n const most = Math.max(0, ...sections.map((section) => section.items))\n const byItems = most > 0 ? sections.findIndex((section) => section.items === most) : -1\n return byItems !== -1 ? byItems : sections.length > 1 ? 1 : 0\n}\n\n/**\n * The section of a store's home its bestsellers fill (AGL-3676): the first\n * after the hero whose name says them (\"Bestsellers\", \"Featured candles\", \"New\n * arrivals\"), never one about the store's collections, its reviews or its\n * sign-up; else the first after the hero naming products at all; else the\n * first after the hero planned with items that is none of those. The\n * beta.237 Willow Wick home would otherwise have listed products in its\n * \"Shop by collection\" tiles.\n */\nfunction aiLayoutFeaturedSection(screen: AiLayoutListingScreen): number {\n // A section about the collections that does not also name the bestsellers.\n const other = (name: string) =>\n (aiStorefrontSays('collections', name) && !aiStorefrontSays('featured', name)) ||\n aiStorefrontSays('reviews', name) ||\n aiStorefrontSays('signup', name)\n const first = (test: (name: string, items: number) => boolean) =>\n screen.sections.findIndex((section, index) => index > 0 && test(section.name, section.items))\n for (const test of [\n (name: string) => aiStorefrontSays('featured', name) && !other(name),\n (name: string) => PRODUCT_SECTION.test(name) && !other(name),\n (name: string, items: number) => items > 0 && !other(name),\n (name: string) => PRODUCT_SECTION.test(name),\n ]) {\n const found = first(test)\n if (found !== -1) return found\n }\n return -1\n}\n\n/** A section of a shop page that only opens or closes it: \"Shop intro heading\", \"Gift help call to action\". */\nconst SHOP_FRAMING = /\\b(hero|intro(?:duction)?|heading|banner|cta|call to action|contact)\\b/i\n\n/** Whether a planned page is the store's own Shop page: its address or its name says it sells. */\nexport function aiLayoutIsShopPage(screen: { slug: string; title: string }): boolean {\n return !isHome(screen.slug) && PRODUCT_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`)\n}\n\nconst firstSegment = (slug: string) => slug.trim().replace(/^\\/+/, '').split('/')[0].toLowerCase()\nconst isHome = (slug: string) => firstSegment(slug) === ''\n\n/**\n * The sections of a site's pages that show a listing, chosen by code from the\n * plan (AGL-3676), never asked of the model:\n *\n * - PRODUCTS. The shop's own page — the page whose address or name says it\n * sells (`/shop`, \"Shop\", \"Products\") — shows the whole catalog in the\n * section the plan gave the most items, else its second; and the home\n * page features a few in its first section after the hero whose name\n * says products (\"Featured candles\", \"Bestsellers\"), else in the first\n * section after the hero planned with items.\n * - POSTS. The home features the latest in its first section of items after\n * the hero whose name says writing (\"Featured writing\", \"Latest posts\"),\n * else its first section of items after the hero; and any other page\n * whose name says it is about the writing does the same. The blog's own\n * index is the platform's.\n *\n * A section placed once is placed for one listing.\n */\nexport function aiLayoutListingPlacements(\n kind: AiLayoutListingKind,\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const placements: AiLayoutListingPlacement[] = []\n const place = (screen: AiLayoutListingScreen, section: number, role: AiLayoutListingRole) => {\n if (typeof screen.id === 'string' && screen.id && section >= 0) placements.push({ screenId: screen.id, section, role })\n }\n const afterHero = (screen: AiLayoutListingScreen, test: (name: string, items: number) => boolean) =>\n screen.sections.findIndex((section, index) => index > 0 && test(section.name, section.items))\n if (kind === 'tracks') {\n // A music site's player (AGL-3716): on its Music page, in the section that\n // says it is about the recordings, else the one after the opening; on the\n // home, only in a section named for them. A site with neither gets it on\n // its home, after the opening.\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n place(screen, afterHero(screen, (name) => MUSIC_PAGE.test(name)), 'featured')\n continue\n }\n if (!MUSIC_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`)) continue\n const named = afterHero(screen, (name) => MUSIC_PAGE.test(name))\n place(screen, named !== -1 ? named : screen.sections.length > 1 ? 1 : 0, 'index')\n }\n if (!placements.length) {\n const home = screens.find((screen) => isHome(screen.slug))\n if (home) place(home, home.sections.length > 1 ? 1 : 0, 'featured')\n }\n return placements\n }\n if (kind === 'reviews' || kind === 'signup') {\n // A store's own reviews, on its home, in the section named for them; its\n // newsletter sign-up in any page's section named for it that places no\n // form of the plan's (AGL-3676). Neither is ever a page's opening.\n for (const screen of screens) {\n if (kind === 'reviews' && !isHome(screen.slug)) continue\n place(\n screen,\n afterHero(\n screen,\n (name) => aiStorefrontSays(kind, name),\n ),\n kind === 'reviews' ? 'featured' : 'index',\n )\n }\n return kind === 'signup'\n ? placements.filter((placement) => {\n const screen = screens.find((entry) => entry.id === placement.screenId)\n return !(screen?.sections[placement.section]?.uses ?? []).some((use) => /form/i.test(use))\n })\n : placements\n }\n if (kind === 'products') {\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n place(screen, aiLayoutFeaturedSection(screen), 'featured')\n continue\n }\n if (!aiLayoutIsShopPage(screen)) continue\n place(screen, aiLayoutShopGridSection(screen.sections), 'index')\n }\n return placements\n }\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n // On a blog's home the band that names the writing (\"Featured posts\n // cards\", \"Featured journeys\") lists it; failing one, the first band of\n // items after the hero. A live Clay Notes plan (2026-10-09) opened its\n // home with \"What you will find\" before \"Featured posts cards\".\n const named = afterHero(screen, (name, items) => items > 0 && POST_SECTION.test(name))\n place(screen, named !== -1 ? named : afterHero(screen, (_name, items) => items > 0), 'featured')\n continue\n }\n if (!POST_SECTION.test(screen.title)) continue\n place(screen, afterHero(screen, (name) => POST_SECTION.test(name)), 'featured')\n }\n return placements\n}\n\n/**\n * Where a dataset's records have pages of their own (AGL-3727): the base of\n * the page the plan makes its record template (AGL-3475), named by its id or\n * as `new:<its name>`; `null` where no page is. Read off the plan, never\n * asked of the model, so every card that lists the dataset links its record.\n */\nexport function aiLayoutRecordBaseOf(\n dataset: { id: string; name: string },\n screens: readonly AiLayoutListingScreen[],\n): string | null {\n const named = `new:${dataset.name}`.trim().toLowerCase()\n for (const screen of screens) {\n const ref = text(screen.record?.dataset)\n if (!ref || (ref !== dataset.id && ref.toLowerCase() !== named)) continue\n const base = text(screen.record?.base).replace(/^\\/+|\\/+$/g, '')\n if (RECORD_BASE.test(base)) return base\n }\n return null\n}\n\n/**\n * The sections that list one of the site's datasets (AGL-3616): every section\n * whose plan names it in its `uses` — `new:<its name>` as the plan wrote it,\n * or its id once built. The home page features a few of its records; any\n * other page lists them all. Chosen from the plan, never asked of the model.\n */\nexport function aiLayoutRecordsPlacements(\n dataset: {\n id: string\n name: string\n /** Its records, by name, as {@link groups} files them. */\n records?: readonly string[]\n /** What its records are filed under: the field, by id, and each record's value, by position. */\n groups?: { field: string; values: readonly string[] } | null\n },\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const named = `new:${dataset.name}`.trim().toLowerCase()\n const placements: AiLayoutListingPlacement[] = []\n for (const screen of screens) {\n if (typeof screen.id !== 'string' || !screen.id) continue\n const here: AiLayoutListingPlacement[] = []\n screen.sections.forEach((section, index) => {\n const lists = (section.uses ?? []).some((ref) => ref === dataset.id || ref.trim().toLowerCase() === named)\n if (lists) here.push({ screenId: screen.id as string, section: index, role: isHome(screen.slug) ? 'featured' : 'index' })\n })\n placements.push(...aiLayoutGroupedPlacements(here, screen, dataset))\n }\n return placements\n}\n\n/** Words a section's name adds to a group's without naming another: \"Family portrait gallery\" is \"Family portraits\". */\nconst GROUP_FRAMING = new Set(\n 'a an and the of our my your for with in at to by from all more recent selected featured favorite gallery galleries portfolio collection collections work works photos photo photographs pictures images showcase grid list cards section'.split(' '),\n)\n\n/** A name's words as a group is matched by: lowercase, a plural's \"-s\" dropped, framing kept. */\nfunction groupWords(name: string): string[] {\n return name\n .toLowerCase()\n .split(/[^a-z0-9À-ɏ]+/)\n .filter(Boolean)\n .map((word) => (word.length > 3 && word.endsWith('ies') ? `${word.slice(0, -3)}y` : word.length > 3 && /[^s]s$/.test(word) ? word.slice(0, -1) : word))\n}\n\n/**\n * The value of a dataset's groups a section's name is named for (AGL-3921),\n * or `null`: every word of the value is in the name (\"Weddings and\n * elopements gallery\" is \"Weddings and elopements\"), else every word of the\n * name past its framing is in the value (\"Weddings\" is \"Weddings and\n * elopements\"), and no other value is named as well. Pure.\n */\nexport function aiLayoutSectionGroup(sectionName: string, values: readonly string[]): string | null {\n const said = groupWords(sectionName)\n const own = said.filter((word) => !GROUP_FRAMING.has(word))\n const distinct = [...new Map(values.filter((value) => value.trim()).map((value) => [value.trim().toLowerCase(), value.trim()])).values()]\n const wordsOf = (value: string) => groupWords(value).filter((word) => !GROUP_FRAMING.has(word))\n const found = distinct.filter((value) => {\n const words = wordsOf(value)\n return (words.length > 0 && words.every((word) => said.includes(word))) || (own.length > 0 && own.every((word) => words.includes(word)))\n })\n if (found.length !== 1) return null\n const value = found[0] as string\n // \"Weddings and family portraits\" names a second group in part: it lists both.\n const others = distinct.filter((other) => other !== value).flatMap(wordsOf)\n const mine = wordsOf(value)\n return own.some((word) => others.includes(word) && !mine.includes(word)) ? null : value\n}\n\n/**\n * A page's placements of one dataset, each section named for one of its\n * groups listing only that group's records (AGL-3921). The beta.265 Laurel\n * Ridge \"Galleries\" page planned \"Weddings and elopements gallery\" and\n * \"Family portrait gallery\" over one \"Gallery collections\" whose records were\n * filed under those two categories, and both sections listed all ten: the\n * plan names no filter, so the build reads it off the sections' names. Only\n * where the page lists the dataset more than once; a section named for no\n * one group lists them all. Pure.\n */\nexport function aiLayoutGroupedPlacements(\n placements: readonly AiLayoutListingPlacement[],\n screen: Pick<AiLayoutListingScreen, 'sections'>,\n dataset: { records?: readonly string[]; groups?: { field: string; values: readonly string[] } | null },\n): AiLayoutListingPlacement[] {\n const groups = dataset.groups\n if (placements.length < 2 || !groups || !FIELD_ID.test(groups.field)) return [...placements]\n return placements.map((placement) => {\n const value = aiLayoutSectionGroup(screen.sections[placement.section]?.name ?? '', groups.values)\n if (!value) return placement\n const records = (dataset.records ?? []).filter((_name, index) => (groups.values[index] ?? '').trim().toLowerCase() === value.toLowerCase())\n return { ...placement, filter: { field: groups.field, value, records } }\n })\n}\n\n/**\n * The sections that list the site's events (AGL-3755): every section whose\n * plan names one of them in its `uses` (`new:<its name>`), once however many\n * it names. The home page features the next few; any other page lists them\n * all. Chosen from the plan, never asked of the model.\n */\nexport function aiLayoutEventsPlacements(\n names: readonly string[],\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const named = new Set(names.map((name) => `new:${name}`.trim().toLowerCase()))\n const placements: AiLayoutListingPlacement[] = []\n for (const screen of screens) {\n if (typeof screen.id !== 'string' || !screen.id) continue\n screen.sections.forEach((section, index) => {\n if ((section.uses ?? []).some((ref) => named.has(ref.trim().toLowerCase()))) {\n placements.push({ screenId: screen.id as string, section: index, role: isHome(screen.slug) ? 'featured' : 'index' })\n }\n })\n }\n return placements\n}\n\n/**\n * A field whose name says it holds what a record's photo shows (AGL-3856):\n * the alt text of the record's Image, never words of its card.\n */\nconst RECORD_IMAGE_ALT = /^(?:image|photo|picture) (?:description|alt(?: text)?)$/i\n\n/** Whether a field's name says it holds what a record's photo shows (AGL-3856). */\nexport function aiLayoutIsRecordImageAlt(name: string): boolean {\n return RECORD_IMAGE_ALT.test(name.trim())\n}\n\n/** The field holding what a record's photo shows, by id, or `null` (AGL-3856). */\nexport function aiLayoutRecordImageAltField(fields: readonly { id: string; name: string }[]): string | null {\n return fields.find((field) => aiLayoutIsRecordImageAlt(field.name))?.id ?? null\n}\n"],"names":["aiStorefrontSays","AI_LAYOUT_LISTINGS_INPUT","aiLayoutListingId","kind","datasetId","AI_LAYOUT_LISTING_ELEMENTS","products","posts","tracks","reviews","signup","records","events","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_FEATURED_RECORDS","PLACEMENT_ROLES","KINDS","text","value","trim","LISTING_NAMES","SITE_PATH","DATASET_ID","FIELD_ID","RECORD_BASE","aiLayoutRecordHref","listing","base","recordBase","replace","field","addressField","test","listingFieldsOf","raw","Array","isArray","flatMap","id","name","slice","type","listingFilterOf","filter","map","Boolean","aiLayoutListingsOf","inputs","listings","entry","record","fields","imageField","includes","some","href","action","actionLabel","actionHref","collectionSlug","placements","placement","screenId","section","role","Number","isInteger","push","cart","emptyAction","label","aiLayoutListingAt","find","aiLayoutRepeatFilterOf","PRODUCT_PAGE","PRODUCT_SECTION","POST_SECTION","MUSIC_PAGE","aiLayoutShopGridSection","sections","named","findIndex","index","SHOP_FRAMING","most","Math","max","items","byItems","length","aiLayoutFeaturedSection","screen","other","first","found","aiLayoutIsShopPage","isHome","slug","firstSegment","title","split","toLowerCase","aiLayoutListingPlacements","screens","place","afterHero","home","uses","use","_name","aiLayoutRecordBaseOf","dataset","ref","aiLayoutRecordsPlacements","here","forEach","lists","aiLayoutGroupedPlacements","GROUP_FRAMING","Set","groupWords","word","endsWith","aiLayoutSectionGroup","sectionName","values","said","own","has","distinct","Map","wordsOf","words","every","others","mine","groups","aiLayoutEventsPlacements","names","RECORD_IMAGE_ALT","aiLayoutIsRecordImageAlt","aiLayoutRecordImageAltField"],"mappings":"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"}
|