@aglyn/plugins-ai 1.0.0-beta.240 → 1.0.0-beta.241
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/components/ai-job-links.d.ts +17 -0
- package/src/lib/components/ai-job-links.js +63 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +8 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +7 -1
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-store-finish-card.component.d.ts +28 -0
- package/src/lib/components/ai-store-finish-card.component.js +116 -0
- package/src/lib/components/ai-store-finish-card.component.js.map +1 -0
- package/src/lib/components/assist-build-card.component.js +12 -1
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +45 -1
- package/src/lib/jobs/ai-job-build-step.js +283 -7
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
- package/src/lib/jobs/ai-job-drafts.js +4 -1
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
- package/src/lib/jobs/ai-job-layout-language.js +4 -2
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +3 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
- package/src/lib/jobs/ai-job-page-language.js +19 -7
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +11 -0
- package/src/lib/jobs/ai-job-page-sections.js +40 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-page-step.js +19 -8
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +12 -0
- package/src/lib/jobs/ai-job-plan-step.js +41 -7
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +76 -2
- package/src/lib/jobs/ai-job-site-datasets.js +206 -13
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +18 -2
- package/src/lib/jobs/ai-job-site-step.js +109 -22
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-store-pages.d.ts +75 -0
- package/src/lib/jobs/ai-job-site-store-pages.js +195 -0
- package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -0
- package/src/lib/jobs/ai-job-store-links-layout.d.ts +71 -0
- package/src/lib/jobs/ai-job-store-links-layout.js +95 -0
- package/src/lib/jobs/ai-job-store-links-layout.js.map +1 -0
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +69 -3
- package/src/lib/jobs/ai-layout-stock-photos.js +603 -42
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-publish.d.ts +18 -0
- package/src/lib/jobs/ai-site-publish.js +76 -15
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.d.ts +43 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js +61 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +25 -2
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +10 -0
- package/src/lib/layout-language/ai-layout-frame.js +58 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +6 -0
- package/src/lib/layout-language/ai-layout-listings.js +3 -0
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +2 -0
- package/src/lib/model/ai-build-job.js +23 -3
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +7 -0
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +3 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-build-store-pages.d.ts +133 -0
- package/src/lib/model/ai-build-store-pages.js +209 -0
- package/src/lib/model/ai-build-store-pages.js.map +1 -0
- package/src/lib/model/ai-layout-store-links.d.ts +64 -0
- package/src/lib/model/ai-layout-store-links.js +165 -0
- package/src/lib/model/ai-layout-store-links.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +5 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-store-pages.d.ts +192 -0
- package/src/lib/model/ai-site-store-pages.js +383 -0
- package/src/lib/model/ai-site-store-pages.js.map +1 -0
- package/src/lib/runtime/site-inventory.js +8 -3
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-index.js +8 -4
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import type { AiStoreFrameLinks } from '../model/ai-site-store-pages';
|
|
17
18
|
import { type AiLayoutSection, type AiLayoutSettlement } from './ai-layout-language';
|
|
18
19
|
import { type AiLayoutPage, type AiLayoutTargets } from './ai-layout-links';
|
|
19
20
|
import { type AiLayoutRawTree } from './ai-layout-tree';
|
|
@@ -58,7 +59,16 @@ export interface AiLayoutFramePlan {
|
|
|
58
59
|
* opens the shop — linking the Shop page.
|
|
59
60
|
*/
|
|
60
61
|
shopId?: string | null;
|
|
62
|
+
/**
|
|
63
|
+
* A store's own pages (AGL-3676), by path: the header links the account
|
|
64
|
+
* beside the cart, and the footer the account and the store's policies
|
|
65
|
+
* under the site's pages. They are written after the layout, so they are
|
|
66
|
+
* linked by their addresses, as the blog is.
|
|
67
|
+
*/
|
|
68
|
+
storeLinks?: AiStoreFrameLinks | null;
|
|
61
69
|
}
|
|
70
|
+
/** What the header's link to a store's account page says (AGL-3676). */
|
|
71
|
+
export declare const AI_LAYOUT_ACCOUNT_LINK = "Account";
|
|
62
72
|
/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */
|
|
63
73
|
export declare const AI_LAYOUT_STORE_ANNOUNCEMENT = "Shop the full collection";
|
|
64
74
|
/** The way into the shop beside the design's own announcement (AGL-3676). */
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
1
|
/**
|
|
3
2
|
* @license
|
|
4
3
|
* Copyright 2026 Aglyn LLC
|
|
@@ -14,13 +13,15 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
14
|
* See the License for the specific language governing permissions and
|
|
16
15
|
* limitations under the License.
|
|
17
|
-
*/ import {
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { AI_ICON_LIBRARY } from "../runtime/ai-icon-library.js";
|
|
18
18
|
import { AI_LAYOUT_MAX_IMAGES, bandSx, compileFlow, compileRow, destinationProps } from "./ai-layout-compiler.js";
|
|
19
19
|
import { aiLayoutFitText } from "./ai-layout-copy.js";
|
|
20
20
|
import { AI_LAYOUT_CART_ELEMENT } from "./ai-layout-listings.js";
|
|
21
21
|
import { AI_LAYOUT_MAX_ITEMS } from "./ai-layout-language.js";
|
|
22
22
|
import { aiLayoutRenamedLabel, aiLayoutResolveLink } from "./ai-layout-links.js";
|
|
23
23
|
import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
24
|
+
/** What the header's link to a store's account page says (AGL-3676). */ export const AI_LAYOUT_ACCOUNT_LINK = 'Account';
|
|
24
25
|
/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */ export const AI_LAYOUT_STORE_ANNOUNCEMENT = 'Shop the full collection';
|
|
25
26
|
/** The way into the shop beside the design's own announcement (AGL-3676). */ export const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = 'Shop now';
|
|
26
27
|
/** The header's colors by band, as palette roles. */ function headerLook(band) {
|
|
@@ -87,8 +88,16 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
90
|
/** Compiles a site's header and footer, designed in the layout language, into its layout. */ export function aiCompileLayoutFrame(frame, plan, targets) {
|
|
90
|
-
var _frame_header, _header_band;
|
|
91
|
+
var _ref, _frame_header, _header_band, _ref1;
|
|
92
|
+
var _plan_storeLinks, _plan_storeLinks1;
|
|
91
93
|
const tree = new AiLayoutTreeBuilder('lf');
|
|
94
|
+
// A store's own pages are destinations the footer's list links by path (AGL-3676).
|
|
95
|
+
const storePages = ((_ref = (_plan_storeLinks = plan.storeLinks) == null ? void 0 : _plan_storeLinks.footer) != null ? _ref : []).map((link)=>({
|
|
96
|
+
id: `store:${link.href}`,
|
|
97
|
+
label: link.label,
|
|
98
|
+
slug: link.href,
|
|
99
|
+
href: link.href
|
|
100
|
+
}));
|
|
92
101
|
const page = {
|
|
93
102
|
tree,
|
|
94
103
|
plan: {
|
|
@@ -96,6 +105,10 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
96
105
|
sections: []
|
|
97
106
|
},
|
|
98
107
|
targets: _extends({}, targets, {
|
|
108
|
+
pages: [
|
|
109
|
+
...targets.pages,
|
|
110
|
+
...storePages
|
|
111
|
+
],
|
|
99
112
|
pageId: null
|
|
100
113
|
}),
|
|
101
114
|
// A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.
|
|
@@ -223,6 +236,21 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
223
236
|
whiteSpace: 'nowrap',
|
|
224
237
|
flexShrink: 0
|
|
225
238
|
}, null, 'cta') : null;
|
|
239
|
+
// A store's account, beside its cart and in the phone's menu (AGL-3676).
|
|
240
|
+
const accountPath = (_ref1 = (_plan_storeLinks1 = plan.storeLinks) == null ? void 0 : _plan_storeLinks1.account) != null ? _ref1 : null;
|
|
241
|
+
const accountLink = (role, interactions, sx)=>{
|
|
242
|
+
const id = tree.add('muiScreenLink', {
|
|
243
|
+
children: AI_LAYOUT_ACCOUNT_LINK,
|
|
244
|
+
href: accountPath,
|
|
245
|
+
renderAs: 'link',
|
|
246
|
+
color: 'inherit'
|
|
247
|
+
}, _extends({
|
|
248
|
+
textDecoration: 'none',
|
|
249
|
+
whiteSpace: 'nowrap'
|
|
250
|
+
}, sx), null, role);
|
|
251
|
+
if (interactions) tree.nodes[id].interactions = interactions;
|
|
252
|
+
return id;
|
|
253
|
+
};
|
|
226
254
|
// A phone's menu: the platform's Drawer, opened by its Menu Button; a link
|
|
227
255
|
// followed from it closes it.
|
|
228
256
|
const close = [
|
|
@@ -242,6 +270,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
242
270
|
typography: 'h6'
|
|
243
271
|
}, [
|
|
244
272
|
...pages.map((entry)=>navLink(entry, 'menuLink', close)),
|
|
273
|
+
accountPath ? accountLink('menuAccount', close) : null,
|
|
245
274
|
ctaProps ? tree.add('muiButton', _extends({}, ctaProps, {
|
|
246
275
|
fullWidth: true
|
|
247
276
|
}), {
|
|
@@ -271,6 +300,14 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
271
300
|
}, {
|
|
272
301
|
flexShrink: 0
|
|
273
302
|
}, null, 'cart') : null;
|
|
303
|
+
// On a phone the account is in the menu, and the cart stays in the bar.
|
|
304
|
+
const account = accountPath ? accountLink('account', undefined, {
|
|
305
|
+
display: {
|
|
306
|
+
xs: 'none',
|
|
307
|
+
md: 'inline-flex'
|
|
308
|
+
},
|
|
309
|
+
flexShrink: 0
|
|
310
|
+
}) : null;
|
|
274
311
|
// The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.
|
|
275
312
|
const row = tree.add('muiContainer', {
|
|
276
313
|
maxWidth: 'lg'
|
|
@@ -282,6 +319,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
282
319
|
brand,
|
|
283
320
|
middle,
|
|
284
321
|
cta,
|
|
322
|
+
account,
|
|
285
323
|
cart,
|
|
286
324
|
toggle,
|
|
287
325
|
drawer
|
|
@@ -393,7 +431,8 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
393
431
|
* written by code: the site's pages, where the design links none of them,
|
|
394
432
|
* and the site's name under a rule.
|
|
395
433
|
*/ function compileFooter(page, footer, plan, name) {
|
|
396
|
-
var _ref, _ref1;
|
|
434
|
+
var _ref, _ref1, _ref2;
|
|
435
|
+
var _plan_storeLinks;
|
|
397
436
|
const tree = page.tree;
|
|
398
437
|
const asked = (_ref = footer == null ? void 0 : footer.band) != null ? _ref : 'soft';
|
|
399
438
|
const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked;
|
|
@@ -483,6 +522,21 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
483
522
|
]);
|
|
484
523
|
weights.push(1);
|
|
485
524
|
}
|
|
525
|
+
// A store's account and policies (AGL-3676), as one more list beside the site's pages.
|
|
526
|
+
const storeLinks = (_ref2 = (_plan_storeLinks = plan.storeLinks) == null ? void 0 : _plan_storeLinks.footer) != null ? _ref2 : [];
|
|
527
|
+
if (storeLinks.length) {
|
|
528
|
+
columns.push([
|
|
529
|
+
{
|
|
530
|
+
kind: 'list',
|
|
531
|
+
items: storeLinks.map((link)=>({
|
|
532
|
+
title: link.label,
|
|
533
|
+
text: '',
|
|
534
|
+
to: `page:store:${link.href}`
|
|
535
|
+
}))
|
|
536
|
+
}
|
|
537
|
+
]);
|
|
538
|
+
weights.push(1);
|
|
539
|
+
}
|
|
486
540
|
const filled = columns.map((column, position)=>{
|
|
487
541
|
var _weights_position;
|
|
488
542
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.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 { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport {\n aiLayoutRenamedLabel,\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n /**\n * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n /**\n * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: boolean\n /**\n * A selling store's Shop page (AGL-3676), by its id. Its header is a\n * storefront's: the cart beside the navigation and no call-to-action button\n * repeating a link the navigation already has, and a thin announcement bar\n * above it — the line the design gave its header as a note, else one that\n * opens the shop — linking the Shop page.\n */\n shopId?: string | null\n}\n\n/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT = 'Shop the full collection'\n\n/** The way into the shop beside the design's own announcement (AGL-3676). */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = 'Shop now'\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n quotesOnly: [],\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n // A store's announcement: the first short line the design gave its header (AGL-3676).\n const store = !!plan.shopId\n const noteBlock = store ? header.blocks.find((block) => block.kind === 'note' || block.kind === 'text' || block.kind === 'lede') : undefined\n for (const block of header.blocks) {\n if (block !== ctaBlock && block !== noteBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : ''\n const ctaResolved =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaResolved)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n // A storefront's header is its navigation and its cart (AGL-3676): the\n // live Willow Wick header's \"Shop candles\" button repeated its nav's Shop.\n // A store's button that only repeats a page the navigation links is left\n // out, and a store that sells carries its cart in that place instead.\n const repeatsNav =\n ctaResolved?.kind === 'page' && pages.some((entry) => !entry.href && entry.id === ctaResolved.screenId)\n const ctaTo = ctaResolved && store && (plan.cart || repeatsNav) ? null : ctaResolved\n if (ctaResolved && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" left out: a store's header carries its ${plan.cart ? 'cart' : 'navigation'}, not a button repeating it`,\n })\n const ctaProps = ctaTo\n ? {\n children: aiLayoutRenamedLabel(ctaLabel, ctaTo),\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, cart, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // A selling store's announcement bar, above the header, linking its shop (AGL-3676).\n const announcement =\n store && plan.cart && plan.shopId\n ? (() => {\n const said = aiLayoutFitText(noteBlock?.text, 'note')\n if (noteBlock && !said) page.settled.push({ at: 'header', what: 'an announcement with no words left; the shop’s own line instead' })\n // The design's line, then the way into the shop; else the way in alone.\n const link = tree.add(\n 'muiScreenLink',\n {\n children: said ? AI_LAYOUT_STORE_ANNOUNCEMENT_LINK : AI_LAYOUT_STORE_ANNOUNCEMENT,\n screenId: plan.shopId,\n renderAs: 'link',\n color: 'inherit',\n },\n { fontWeight: 700 },\n null,\n 'announcementLink',\n )\n const words = said\n ? tree.add('muiTypography', { children: said, variant: 'body2', component: 'p' }, null, null, 'announcementText')\n : null\n return tree.add(\n 'muiStack',\n { direction: 'row', spacing: '1.5', justifyContent: 'center', alignItems: 'baseline', useFlexGap: true, flexWrap: 'wrap' },\n { bgcolor: 'primary.main', color: 'primary.contrastText', typography: 'body2', py: 1, px: 2 },\n [words, link],\n 'announcement',\n )\n })()\n : null\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [announcement, bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n const top =\n filled.length > 1\n ? compileRow(scope, filled)\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","aiLayoutRenamedLabel","aiLayoutResolveLink","AiLayoutTreeBuilder","AI_LAYOUT_STORE_ANNOUNCEMENT","AI_LAYOUT_STORE_ANNOUNCEMENT_LINK","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","title","siteName","pageId","options","reusableComponents","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","design","pictures","features","splitAt","quotesOnly","name","blocks","look","scope","navLink","entry","role","interactions","id","add","children","label","slice","href","screenId","renderAs","textDecoration","nodes","pages","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","map","ctaBlock","find","block","kind","store","shopId","noteBlock","undefined","push","what","ctaLabel","ctaResolved","to","repeatsNav","some","ctaTo","cart","ctaProps","cta","sm","flexShrink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","overHero","announcement","said","words","useFlexGap","flexWrap","py","px","slot","footer","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","asked","closesDark","navItems","linksPages","flatMap","given","notice","item","items","has","pageLinks","cols","columns","col","Math","min","weights","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","element","colorScheme"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.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 type { AiStoreFrameLinks } from '../model/ai-site-store-pages'\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport {\n aiLayoutRenamedLabel,\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n /**\n * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n /**\n * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: boolean\n /**\n * A selling store's Shop page (AGL-3676), by its id. Its header is a\n * storefront's: the cart beside the navigation and no call-to-action button\n * repeating a link the navigation already has, and a thin announcement bar\n * above it — the line the design gave its header as a note, else one that\n * opens the shop — linking the Shop page.\n */\n shopId?: string | null\n /**\n * A store's own pages (AGL-3676), by path: the header links the account\n * beside the cart, and the footer the account and the store's policies\n * under the site's pages. They are written after the layout, so they are\n * linked by their addresses, as the blog is.\n */\n storeLinks?: AiStoreFrameLinks | null\n}\n\n/** What the header's link to a store's account page says (AGL-3676). */\nexport const AI_LAYOUT_ACCOUNT_LINK = 'Account'\n\n/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT = 'Shop the full collection'\n\n/** The way into the shop beside the design's own announcement (AGL-3676). */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = 'Shop now'\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n // A store's own pages are destinations the footer's list links by path (AGL-3676).\n const storePages: AiLayoutPage[] = (plan.storeLinks?.footer ?? []).map((link) => ({\n id: `store:${link.href}`,\n label: link.label,\n slug: link.href,\n href: link.href,\n }))\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pages: [...targets.pages, ...storePages], pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n quotesOnly: [],\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n // A store's announcement: the first short line the design gave its header (AGL-3676).\n const store = !!plan.shopId\n const noteBlock = store ? header.blocks.find((block) => block.kind === 'note' || block.kind === 'text' || block.kind === 'lede') : undefined\n for (const block of header.blocks) {\n if (block !== ctaBlock && block !== noteBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : ''\n const ctaResolved =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaResolved)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n // A storefront's header is its navigation and its cart (AGL-3676): the\n // live Willow Wick header's \"Shop candles\" button repeated its nav's Shop.\n // A store's button that only repeats a page the navigation links is left\n // out, and a store that sells carries its cart in that place instead.\n const repeatsNav =\n ctaResolved?.kind === 'page' && pages.some((entry) => !entry.href && entry.id === ctaResolved.screenId)\n const ctaTo = ctaResolved && store && (plan.cart || repeatsNav) ? null : ctaResolved\n if (ctaResolved && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" left out: a store's header carries its ${plan.cart ? 'cart' : 'navigation'}, not a button repeating it`,\n })\n const ctaProps = ctaTo\n ? {\n children: aiLayoutRenamedLabel(ctaLabel, ctaTo),\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A store's account, beside its cart and in the phone's menu (AGL-3676).\n const accountPath = plan.storeLinks?.account ?? null\n const accountLink = (role: string, interactions?: unknown[], sx?: Record<string, unknown>) => {\n const id = tree.add(\n 'muiScreenLink',\n { children: AI_LAYOUT_ACCOUNT_LINK, href: accountPath, renderAs: 'link', color: 'inherit' },\n { textDecoration: 'none', whiteSpace: 'nowrap', ...sx },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n accountPath ? accountLink('menuAccount', close) : null,\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\n // On a phone the account is in the menu, and the cart stays in the bar.\n const account = accountPath ? accountLink('account', undefined, { display: { xs: 'none', md: 'inline-flex' }, flexShrink: 0 }) : null\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, account, cart, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // A selling store's announcement bar, above the header, linking its shop (AGL-3676).\n const announcement =\n store && plan.cart && plan.shopId\n ? (() => {\n const said = aiLayoutFitText(noteBlock?.text, 'note')\n if (noteBlock && !said) page.settled.push({ at: 'header', what: 'an announcement with no words left; the shop’s own line instead' })\n // The design's line, then the way into the shop; else the way in alone.\n const link = tree.add(\n 'muiScreenLink',\n {\n children: said ? AI_LAYOUT_STORE_ANNOUNCEMENT_LINK : AI_LAYOUT_STORE_ANNOUNCEMENT,\n screenId: plan.shopId,\n renderAs: 'link',\n color: 'inherit',\n },\n { fontWeight: 700 },\n null,\n 'announcementLink',\n )\n const words = said\n ? tree.add('muiTypography', { children: said, variant: 'body2', component: 'p' }, null, null, 'announcementText')\n : null\n return tree.add(\n 'muiStack',\n { direction: 'row', spacing: '1.5', justifyContent: 'center', alignItems: 'baseline', useFlexGap: true, flexWrap: 'wrap' },\n { bgcolor: 'primary.main', color: 'primary.contrastText', typography: 'body2', py: 1, px: 2 },\n [words, link],\n 'announcement',\n )\n })()\n : null\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [announcement, bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n // A store's account and policies (AGL-3676), as one more list beside the site's pages.\n const storeLinks = plan.storeLinks?.footer ?? []\n if (storeLinks.length) {\n columns.push([{ kind: 'list', items: storeLinks.map((link) => ({ title: link.label, text: '', to: `page:store:${link.href}` })) }])\n weights.push(1)\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n const top =\n filled.length > 1\n ? compileRow(scope, filled)\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","aiLayoutRenamedLabel","aiLayoutResolveLink","AiLayoutTreeBuilder","AI_LAYOUT_ACCOUNT_LINK","AI_LAYOUT_STORE_ANNOUNCEMENT","AI_LAYOUT_STORE_ANNOUNCEMENT_LINK","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","storePages","storeLinks","footer","map","id","href","label","slug","title","siteName","pages","pageId","options","reusableComponents","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","design","pictures","features","splitAt","quotesOnly","name","blocks","look","scope","navLink","entry","role","interactions","add","children","slice","screenId","renderAs","textDecoration","nodes","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","ctaBlock","find","block","kind","store","shopId","noteBlock","undefined","push","what","ctaLabel","ctaResolved","to","repeatsNav","some","ctaTo","cart","ctaProps","cta","sm","flexShrink","accountPath","account","accountLink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","overHero","announcement","said","words","useFlexGap","flexWrap","py","px","slot","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","asked","closesDark","navItems","linksPages","flatMap","given","notice","item","items","has","pageLinks","cols","columns","col","Math","min","weights","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","element","colorScheme"],"mappings":"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"}
|
|
@@ -104,6 +104,12 @@ export interface AiLayoutListing {
|
|
|
104
104
|
datasetId?: string;
|
|
105
105
|
/** The dataset's fields, in its order, which the card binds. */
|
|
106
106
|
fields?: AiLayoutListingField[];
|
|
107
|
+
/**
|
|
108
|
+
* The field holding each record's photo (AGL-3616), which the card leads
|
|
109
|
+
* with as its Image's `src`, so a portfolio stays a gallery. Never among
|
|
110
|
+
* `fields`: it is the card's picture, not its words.
|
|
111
|
+
*/
|
|
112
|
+
imageField?: string;
|
|
107
113
|
/**
|
|
108
114
|
* Where a visitor goes from a store with nothing listed yet (AGL-3676): the
|
|
109
115
|
* site's contact page, by its path. The empty state names it as its one
|
|
@@ -87,6 +87,7 @@ const FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
|
87
87
|
// A records listing is one dataset's (AGL-3616): a site may list several.
|
|
88
88
|
const datasetId = kind === 'records' ? text(record['datasetId']) : '';
|
|
89
89
|
const fields = kind === 'records' ? listingFieldsOf(record['fields']) : [];
|
|
90
|
+
const imageField = kind === 'records' ? text(record['imageField']) : '';
|
|
90
91
|
// Its fields may be left for the page to read off the site's inventory.
|
|
91
92
|
if (kind === 'records' && !DATASET_ID.test(datasetId)) continue;
|
|
92
93
|
if (!KINDS.includes(kind) || listings.some((listing)=>listing.kind === kind && (kind !== 'records' || listing.datasetId === datasetId))) {
|
|
@@ -126,6 +127,8 @@ const FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
|
126
127
|
} : {}, kind === 'records' ? {
|
|
127
128
|
datasetId,
|
|
128
129
|
fields
|
|
130
|
+
} : {}, kind === 'records' && FIELD_ID.test(imageField) ? {
|
|
131
|
+
imageField
|
|
129
132
|
} : {}, kind === 'products' && record['cart'] === false ? {
|
|
130
133
|
cart: false
|
|
131
134
|
} : {}, kind === 'products' && actionLabel && SITE_PATH.test(actionHref) ? {
|
|
@@ -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 *\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'\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 * 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}\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 } as const\n\nconst PLACEMENT_ROLES: readonly AiLayoutListingRole[] = ['featured', 'index']\nconst KINDS: readonly AiLayoutListingKind[] = ['products', 'posts', 'tracks', 'reviews', 'signup', 'records']\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}\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 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 // 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'\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 === '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}\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 * 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"],"names":["aiStorefrontSays","AI_LAYOUT_LISTINGS_INPUT","aiLayoutListingId","kind","datasetId","AI_LAYOUT_LISTING_ELEMENTS","products","posts","tracks","reviews","signup","records","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_FEATURED_RECORDS","PLACEMENT_ROLES","KINDS","text","value","trim","LISTING_NAMES","SITE_PATH","DATASET_ID","FIELD_ID","listingFieldsOf","raw","Array","isArray","flatMap","field","id","test","name","slice","type","aiLayoutListingsOf","inputs","listings","entry","record","fields","includes","some","listing","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","replace","split","toLowerCase","aiLayoutListingPlacements","screens","place","afterHero","home","uses","use","_name","aiLayoutRecordsPlacements","dataset","forEach","lists","ref"],"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 *\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'\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 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}\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 } as const\n\nconst PLACEMENT_ROLES: readonly AiLayoutListingRole[] = ['featured', 'index']\nconst KINDS: readonly AiLayoutListingKind[] = ['products', 'posts', 'tracks', 'reviews', 'signup', 'records']\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}\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 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 // 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'\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 ...(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}\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 * 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"],"names":["aiStorefrontSays","AI_LAYOUT_LISTINGS_INPUT","aiLayoutListingId","kind","datasetId","AI_LAYOUT_LISTING_ELEMENTS","products","posts","tracks","reviews","signup","records","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_FEATURED_RECORDS","PLACEMENT_ROLES","KINDS","text","value","trim","LISTING_NAMES","SITE_PATH","DATASET_ID","FIELD_ID","listingFieldsOf","raw","Array","isArray","flatMap","field","id","test","name","slice","type","aiLayoutListingsOf","inputs","listings","entry","record","fields","imageField","includes","some","listing","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","replace","split","toLowerCase","aiLayoutListingPlacements","screens","place","afterHero","home","uses","use","_name","aiLayoutRecordsPlacements","dataset","forEach","lists","ref"],"mappings":"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"}
|
|
@@ -84,6 +84,8 @@ export declare function aiBuildScreenRefs(screen: AiBuildPlanScreen): string[];
|
|
|
84
84
|
* their dependencies.
|
|
85
85
|
*/
|
|
86
86
|
export declare function aiBuildUnits(plan: AiBuildPlan): AiBuildUnit[];
|
|
87
|
+
/** Whether a unit is one the platform adds rather than the plan (AGL-3676): a store's own pages. */
|
|
88
|
+
export declare function aiBuildUnitIsPlatform(unit: Pick<AiBuildUnit, 'op'>): boolean;
|
|
87
89
|
/**
|
|
88
90
|
* The units in an order that builds every dependency before what depends on
|
|
89
91
|
* it, plan order otherwise; `null` when the dependencies hold a cycle.
|