@aglyn/plugins-ai 1.0.0-beta.243 → 1.0.0-beta.246
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/activity/ai-activity-actions.js +3 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-links.js +7 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +3 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-hub-card.component.js +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.js +17 -9
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +6 -4
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.js +162 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-build-step.js +85 -16
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-duplicate-step.d.ts +123 -0
- package/src/lib/jobs/ai-job-duplicate-step.js +269 -0
- package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-language.js +24 -6
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-navigation-step.d.ts +157 -0
- package/src/lib/jobs/ai-job-navigation-step.js +439 -0
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +5 -2
- package/src/lib/jobs/ai-job-page-language.js +38 -10
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +1 -1
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +11 -4
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-plan-step.js +16 -7
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +2 -2
- package/src/lib/jobs/ai-job-products-step.js +2 -2
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +272 -0
- package/src/lib/jobs/ai-job-settings-step.js +695 -0
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -0
- package/src/lib/jobs/ai-job-site-content.d.ts +20 -6
- package/src/lib/jobs/ai-job-site-content.js +28 -12
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +2 -0
- package/src/lib/jobs/ai-job-site-datasets.js +10 -3
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +3 -1
- package/src/lib/jobs/ai-job-site-step.js +86 -12
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-site-publish.d.ts +76 -4
- package/src/lib/jobs/ai-site-publish.js +231 -59
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +7 -4
- package/src/lib/layout-language/ai-layout-compiler.js +26 -25
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame-hollows.d.ts +41 -0
- package/src/lib/layout-language/ai-layout-frame-hollows.js +330 -0
- package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +18 -1
- package/src/lib/layout-language/ai-layout-frame.js +83 -13
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-links.js +5 -2
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +35 -0
- package/src/lib/layout-language/ai-layout-listings.js +34 -0
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-tree.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-tree.js +17 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +23 -0
- package/src/lib/model/ai-build-job.js +102 -13
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +25 -0
- package/src/lib/model/ai-build-plan.js +45 -1
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +52 -0
- package/src/lib/model/ai-credit-estimate.js +51 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.js +4 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +10 -2
- package/src/lib/model/ai-jobs.types.js +4 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-layout-menus.d.ts +104 -0
- package/src/lib/model/ai-layout-menus.js +518 -0
- package/src/lib/model/ai-layout-menus.js.map +1 -0
- package/src/lib/model/ai-products.d.ts +31 -1
- package/src/lib/model/ai-products.js +34 -1
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/model/ai-site-inventory.d.ts +15 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +10 -0
- package/src/lib/model/ai-site-job.js +36 -9
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-store-pages.js +1 -1
- package/src/lib/model/ai-site-store-pages.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +95 -1
- package/src/lib/runtime/ai-doctrine-validators.js +264 -36
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +7 -2
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +1 -1
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-products-generation.d.ts +4 -2
- package/src/lib/runtime/ai-products-generation.js +12 -6
- package/src/lib/runtime/ai-products-generation.js.map +1 -1
- package/src/lib/runtime/site-inventory.d.ts +12 -0
- package/src/lib/runtime/site-inventory.js +29 -1
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server.js +12 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-index.js +37 -3
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
- package/src/lib/tools/ai-products-tool.d.ts +3 -1
- package/src/lib/tools/ai-products-tool.js +15 -5
- package/src/lib/tools/ai-products-tool.js.map +1 -1
- package/src/lib/usage/ai-quota-checks.d.ts +13 -3
- package/src/lib/usage/ai-quota-checks.js +46 -8
- package/src/lib/usage/ai-quota-checks.js.map +1 -1
|
@@ -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 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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"}
|
|
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 { aiLayoutRepairFrameHollows } from './ai-layout-frame-hollows'\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 // No heading with nothing under it, no column of only a heading, columns\n // top-aligned (AGL-3735): a list under a heading can compile to nothing.\n if (aiLayoutRepairFrameHollows(tree.nodes as unknown as Record<string, unknown>)) {\n page.settled.push({ at: 'footer', what: 'a heading with nothing under it taken out, and its column with it' })\n }\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/** What a store's own column of account and policy links is headed where the footer design gave no store heading (AGL-3735). */\nexport const AI_LAYOUT_STORE_FOOTER_HEADING = 'Customer care'\n\n/** A footer heading a store's account and policies belong under (AGL-3735). */\nconst STORE_HEADING = /\\b(?:shop|store|shopping|help|customer|care|support|service|orders?|account|policies|policy|legal|info|information|shipping|returns)\\b/i\n\n/**\n * The footer heading a store's account and policy links go under\n * (AGL-3735): a heading of the design's that names the store or its help\n * (\"Our shop\", \"Help\", \"Customer care\"), the first with nothing under it\n * before one with words or links of its own. Never the heading over the\n * site's pages, whose list is the site's navigation. `end` is where the\n * links go: the end of what that heading heads.\n */\nexport function aiLayoutFooterStoreHeading(\n columns: readonly AiLayoutBlock[][],\n navItems: readonly { to: string }[],\n): { column: AiLayoutBlock[]; end: number; heading: string } | null {\n const navTo = new Set(navItems.map((item) => item.to))\n const candidates: Array<{ column: AiLayoutBlock[]; end: number; heading: string; empty: boolean }> = []\n for (const column of columns) {\n column.forEach((block, at) => {\n if (block.kind !== 'heading' || !STORE_HEADING.test(block.text ?? '')) return\n let end = at + 1\n while (end < column.length && column[end].kind !== 'heading') end += 1\n const under = column.slice(at + 1, end)\n const site = under.some((one) => one.kind === 'list' && (one.items ?? []).some((item) => navTo.has(item.to ?? '')))\n if (site) return\n candidates.push({ column: column as AiLayoutBlock[], end, heading: String(block.text), empty: under.length === 0 })\n })\n }\n const pick = candidates.find((one) => one.empty) ?? candidates[0]\n return pick ? { column: pick.column, end: pick.end, heading: pick.heading } : null\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): under the store heading the\n // design already wrote (\"Our shop\", \"Help\"…), never as a column with no\n // heading beside a heading with nothing under it (AGL-3735); where the\n // design wrote none, a column of their own, headed.\n const storeLinks = plan.storeLinks?.footer ?? []\n if (storeLinks.length) {\n const list: AiLayoutBlock = { kind: 'list', items: storeLinks.map((link) => ({ title: link.label, text: '', to: `page:store:${link.href}` })) }\n const home = aiLayoutFooterStoreHeading(columns, navItems)\n if (home) {\n home.column.splice(home.end, 0, list)\n page.settled.push({ at: 'footer', what: `the store's account and policy links under \"${home.heading}\"` })\n } else {\n columns.push([{ kind: 'heading', text: AI_LAYOUT_STORE_FOOTER_HEADING }, list])\n weights.push(1)\n }\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n // A footer's columns are top-aligned: each starts at its heading (AGL-3735).\n const top =\n filled.length > 1\n ? compileRow(scope, filled, 'flex-start')\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","aiLayoutRepairFrameHollows","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","AI_LAYOUT_STORE_FOOTER_HEADING","STORE_HEADING","aiLayoutFooterStoreHeading","columns","navItems","candidates","navTo","item","forEach","end","under","site","one","items","has","heading","String","empty","pick","enabled","trigger","event","everyTime","steps","type","asked","closesDark","linksPages","flatMap","given","notice","pageLinks","cols","col","Math","min","weights","list","home","splice","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","element","colorScheme"],"mappings":"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"}
|
|
@@ -14,7 +14,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import {
|
|
17
|
+
*/ import { aiAsksToGetInTouch } from "../runtime/ai-doctrine-validators.js";
|
|
18
|
+
import { aiPageLinkSection } from "../runtime/ai-page-links.js";
|
|
18
19
|
/**
|
|
19
20
|
* A link's words for where it now goes (AGL-3676): a button that named a
|
|
20
21
|
* page merged into the blog — "Read the articles" — names the blog instead,
|
|
@@ -37,7 +38,9 @@ function slugOf(slug) {
|
|
|
37
38
|
return `/${slug.replace(/^\/+|\/+$/g, '')}`.toLowerCase();
|
|
38
39
|
}
|
|
39
40
|
const HOME_WORDS = /\bhome(?:\s?page)?\b/i;
|
|
40
|
-
/** Words that ask a visitor to get in touch, which the site's form answers. */ const CONTACT_WORDS =
|
|
41
|
+
/** Words that ask a visitor to get in touch, which the site's form answers: rule 10's own list (AGL-3729). */ const CONTACT_WORDS = {
|
|
42
|
+
test: aiAsksToGetInTouch
|
|
43
|
+
};
|
|
41
44
|
/** The page a word names: an id, a label or an address. */ function pageNamed(word, targets) {
|
|
42
45
|
var _targets_pages_find, _ref, _targets_pages_find1;
|
|
43
46
|
const value = word.trim();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-links.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 { aiPageLinkSection } from '../runtime/ai-page-links'\nimport type { AiLayoutListing } from './ai-layout-listings'\n\n/**\n * Where a link the layout language names goes (AGL-3660), resolved by code to\n * a destination rule 10 admits: a page of this site by its id, a section of\n * this page by the platform's Scroll to element interaction, or an `https:`\n * address the brief itself gave. A destination that resolves to nothing, or\n * to the home page under words that do not say home, is no destination, and\n * the compiler leaves the button out rather than shipping a dead control.\n */\n\n/** A page of the site a link may go to: built, or minted on a guided start's plan. */\nexport interface AiLayoutPage {\n id: string\n label: string\n slug: string\n /**\n * A destination on the site that is no page of its own, linked by its path:\n * the blog a guided start writes its posts into (AGL-3660), whose `id` is\n * no screen. Absent for every page.\n */\n href?: string\n /**\n * The title of a planned page merged into this destination (AGL-3676): a\n * page that stood in for the blog, kept only so links to it go to the blog.\n * A link to it is named for the blog, not for the page it replaced.\n */\n standsInFor?: string\n}\n\n/** Everything a page's links resolve against. */\nexport interface AiLayoutTargets {\n /** The page being built, which no link of its own goes to. */\n pageId?: string | null\n /** The site's pages a link may go to. */\n pages: readonly AiLayoutPage[]\n /** The site's home pages, which only words that say home link (rule 10). */\n homeIds: readonly string[]\n /** The saved forms a page may place. */\n forms: readonly { id: string; name: string }[]\n /** The page that places the site's form, where that is another page. */\n formPageId?: string | null\n /** The reusable components a page may place, with the props each fills. */\n components: readonly {\n id: string\n name: string\n props: Record<string, string>\n }[]\n /** Every word the job was given: the brief, the site's answers, its profile. */\n facts: string\n /**\n * The kinds of record the site keeps that its pages show — its catalog,\n * its blog — and the sections that place each (AGL-3676,\n * `ai-layout-listings.ts`). Absent or empty, a page shows only its words.\n */\n listings?: readonly AiLayoutListing[]\n}\n\n/** Where a link resolved to. */\nexport type AiLayoutDestination =\n | { kind: 'page'; screenId: string }\n | { kind: 'section'; index: number }\n | { kind: 'href'; href: string }\n /**\n * A path on this site that is no page of its own, such as the blog\n * (AGL-3660); reached through a planned page merged into it, the title\n * that page had and the name it now goes by (AGL-3676).\n */\n | { kind: 'path'; href: string; standsInFor?: string; label?: string }\n\n/**\n * A link's words for where it now goes (AGL-3676): a button that named a\n * page merged into the blog — \"Read the articles\" — names the blog instead,\n * \"Read the blog\". Words that name neither are kept.\n */\nexport function aiLayoutRenamedLabel(label: string, destination: AiLayoutDestination): string {\n if (destination.kind !== 'path' || !destination.standsInFor || !destination.label) return label\n const name = destination.label.toLowerCase()\n const words = wordsOf(destination.standsInFor)\n if (!words.length) return label\n // The page's own name, or its words alone, singular or plural.\n const stems = words.map((word) => word.replace(/s$/, ''))\n const pattern = new RegExp(`\\\\b(?:${stems.map((stem) => stem.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')).join('\\\\s+')})s?\\\\b`, 'giu')\n const renamed = label.replace(pattern, (found) => (found[0] === found[0].toUpperCase() ? destination.label as string : name))\n return renamed\n}\n\n/** The words of a label or a name, as they are compared. */\nfunction wordsOf(text: string): string[] {\n return text\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean)\n}\n\nfunction slugOf(slug: string): string {\n return `/${slug.replace(/^\\/+|\\/+$/g, '')}`.toLowerCase()\n}\n\nconst HOME_WORDS = /\\bhome(?:\\s?page)?\\b/i\n\n/** Words that ask a visitor to get in touch, which the site's form answers. */\nconst CONTACT_WORDS =\n /\\b(?:contact|book|booking|call|quote|estimate|appointment|schedule|reserve|reservation|get in touch|request|visit|enquire|inquire|enquiry|inquiry|message|talk|consult|consultation|order|sign up|join|apply|start)\\b/i\n\nexport interface AiLayoutLinkScope {\n /** The page's section names in plan order. */\n sections: readonly string[]\n /** The section the link sits in, which it does not scroll to. */\n from: number\n /** The section of this page that places the site's form, if one does. */\n formSection: number | null\n}\n\n/** The page a word names: an id, a label or an address. */\nfunction pageNamed(\n word: string,\n targets: AiLayoutTargets,\n): AiLayoutPage | null {\n const value = word.trim()\n if (!value) return null\n const byId = targets.pages.find((page) => page.id === value)\n if (byId) return byId\n const lower = value.toLowerCase()\n if (lower.startsWith('/'))\n return (\n targets.pages.find((page) => slugOf(page.slug) === slugOf(lower)) ?? null\n )\n const words = wordsOf(value).join(' ')\n return (\n targets.pages.find((page) => wordsOf(page.label).join(' ') === words) ??\n // A page merged into the blog is still named by the title it had (AGL-3676).\n targets.pages.find((page) => page.standsInFor && wordsOf(page.standsInFor).join(' ') === words) ??\n null\n )\n}\n\n/** A destination rule 10 admits, from the page it names, for the words a link shows. */\nfunction pageDestination(\n page: AiLayoutPage | null,\n label: string,\n targets: AiLayoutTargets,\n): AiLayoutDestination | null {\n if (!page || page.id === targets.pageId) return null\n if (\n targets.homeIds.includes(page.id) &&\n !HOME_WORDS.test(label) &&\n !isHomeSlug(page.slug)\n )\n return null\n if (isHomeSlug(page.slug) && !HOME_WORDS.test(label)) return null\n if (page.href) return { kind: 'path', href: page.href, ...(page.standsInFor ? { standsInFor: page.standsInFor, label: page.label } : {}) }\n return { kind: 'page', screenId: page.id }\n}\n\nfunction isHomeSlug(slug: string): boolean {\n return slugOf(slug) === '/'\n}\n\n/** Where the site's form is: a section of this page, else the page that places it. */\nfunction formDestination(\n scope: AiLayoutLinkScope,\n targets: AiLayoutTargets,\n): AiLayoutDestination | null {\n if (scope.formSection !== null && scope.formSection !== scope.from)\n return { kind: 'section', index: scope.formSection }\n if (targets.formPageId && targets.formPageId !== targets.pageId) {\n return { kind: 'page', screenId: targets.formPageId }\n }\n return null\n}\n\n/**\n * Where a link goes: the destination its `to` names, read the language's\n * way; failing that, for words that ask a visitor to get in touch, the site's\n * form; failing that, a page whose name the words say. `null` is a link with\n * nowhere to go, which is left out.\n */\nexport function aiLayoutResolveLink(\n to: string | undefined,\n label: string,\n scope: AiLayoutLinkScope,\n targets: AiLayoutTargets,\n): AiLayoutDestination | null {\n const named = to ? resolveNamed(to.trim(), label, scope, targets) : null\n if (named) return named\n if (CONTACT_WORDS.test(label)) {\n const form = formDestination(scope, targets)\n if (form) return form\n const contact = targets.pages.find(\n (page) =>\n page.id !== targets.pageId &&\n !isHomeSlug(page.slug) &&\n CONTACT_WORDS.test(page.label),\n )\n if (contact) return { kind: 'page', screenId: contact.id }\n }\n const labelWords = new Set(wordsOf(label))\n const said = targets.pages.filter(\n (page) =>\n page.id !== targets.pageId &&\n !isHomeSlug(page.slug) &&\n wordsOf(page.label).length > 0 &&\n wordsOf(page.label).every((word) => labelWords.has(word)),\n )\n return said.length === 1 ? { kind: 'page', screenId: said[0].id } : null\n}\n\nfunction resolveNamed(\n to: string,\n label: string,\n scope: AiLayoutLinkScope,\n targets: AiLayoutTargets,\n): AiLayoutDestination | null {\n const lower = to.toLowerCase()\n if (\n lower === 'form' ||\n lower.startsWith('form:') ||\n targets.forms.some((form) => form.id === to)\n ) {\n return formDestination(scope, targets)\n }\n if (lower.startsWith('https://')) {\n return targets.facts.includes(to) ? { kind: 'href', href: to } : null\n }\n const section = lower.startsWith('#')\n ? to.slice(1)\n : lower.startsWith('section:')\n ? to.slice(8)\n : null\n if (section !== null) {\n const number = Number(section.trim())\n const index =\n Number.isInteger(number) && number >= 1 && number <= scope.sections.length\n ? number - 1\n : aiPageLinkSection(section, scope.sections)\n return index !== null && index !== scope.from\n ? { kind: 'section', index }\n : null\n }\n const page = pageNamed(lower.startsWith('page:') ? to.slice(5) : to, targets)\n return pageDestination(page, label, targets)\n}\n"],"names":["aiPageLinkSection","aiLayoutRenamedLabel","label","destination","kind","standsInFor","name","toLowerCase","words","wordsOf","length","stems","map","word","replace","pattern","RegExp","stem","join","renamed","found","toUpperCase","text","split","filter","Boolean","slugOf","slug","HOME_WORDS","CONTACT_WORDS","pageNamed","targets","value","trim","byId","pages","find","page","id","lower","startsWith","pageDestination","pageId","homeIds","includes","test","isHomeSlug","href","screenId","formDestination","scope","formSection","from","index","formPageId","aiLayoutResolveLink","to","named","resolveNamed","form","contact","labelWords","Set","said","every","has","forms","some","facts","section","slice","number","Number","isInteger","sections"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-links.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 { aiAsksToGetInTouch } from '../runtime/ai-doctrine-validators'\nimport { aiPageLinkSection } from '../runtime/ai-page-links'\nimport type { AiLayoutListing } from './ai-layout-listings'\n\n/**\n * Where a link the layout language names goes (AGL-3660), resolved by code to\n * a destination rule 10 admits: a page of this site by its id, a section of\n * this page by the platform's Scroll to element interaction, or an `https:`\n * address the brief itself gave. A destination that resolves to nothing, or\n * to the home page under words that do not say home, is no destination, and\n * the compiler leaves the button out rather than shipping a dead control.\n */\n\n/** A page of the site a link may go to: built, or minted on a guided start's plan. */\nexport interface AiLayoutPage {\n id: string\n label: string\n slug: string\n /**\n * A destination on the site that is no page of its own, linked by its path:\n * the blog a guided start writes its posts into (AGL-3660), whose `id` is\n * no screen. Absent for every page.\n */\n href?: string\n /**\n * The title of a planned page merged into this destination (AGL-3676): a\n * page that stood in for the blog, kept only so links to it go to the blog.\n * A link to it is named for the blog, not for the page it replaced.\n */\n standsInFor?: string\n}\n\n/** Everything a page's links resolve against. */\nexport interface AiLayoutTargets {\n /** The page being built, which no link of its own goes to. */\n pageId?: string | null\n /** The site's pages a link may go to. */\n pages: readonly AiLayoutPage[]\n /** The site's home pages, which only words that say home link (rule 10). */\n homeIds: readonly string[]\n /** The saved forms a page may place. */\n forms: readonly { id: string; name: string }[]\n /** The page that places the site's form, where that is another page. */\n formPageId?: string | null\n /** The reusable components a page may place, with the props each fills. */\n components: readonly {\n id: string\n name: string\n props: Record<string, string>\n }[]\n /** Every word the job was given: the brief, the site's answers, its profile. */\n facts: string\n /**\n * The kinds of record the site keeps that its pages show — its catalog,\n * its blog — and the sections that place each (AGL-3676,\n * `ai-layout-listings.ts`). Absent or empty, a page shows only its words.\n */\n listings?: readonly AiLayoutListing[]\n}\n\n/** Where a link resolved to. */\nexport type AiLayoutDestination =\n | { kind: 'page'; screenId: string }\n | { kind: 'section'; index: number }\n | { kind: 'href'; href: string }\n /**\n * A path on this site that is no page of its own, such as the blog\n * (AGL-3660); reached through a planned page merged into it, the title\n * that page had and the name it now goes by (AGL-3676).\n */\n | { kind: 'path'; href: string; standsInFor?: string; label?: string }\n\n/**\n * A link's words for where it now goes (AGL-3676): a button that named a\n * page merged into the blog — \"Read the articles\" — names the blog instead,\n * \"Read the blog\". Words that name neither are kept.\n */\nexport function aiLayoutRenamedLabel(label: string, destination: AiLayoutDestination): string {\n if (destination.kind !== 'path' || !destination.standsInFor || !destination.label) return label\n const name = destination.label.toLowerCase()\n const words = wordsOf(destination.standsInFor)\n if (!words.length) return label\n // The page's own name, or its words alone, singular or plural.\n const stems = words.map((word) => word.replace(/s$/, ''))\n const pattern = new RegExp(`\\\\b(?:${stems.map((stem) => stem.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')).join('\\\\s+')})s?\\\\b`, 'giu')\n const renamed = label.replace(pattern, (found) => (found[0] === found[0].toUpperCase() ? destination.label as string : name))\n return renamed\n}\n\n/** The words of a label or a name, as they are compared. */\nfunction wordsOf(text: string): string[] {\n return text\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean)\n}\n\nfunction slugOf(slug: string): string {\n return `/${slug.replace(/^\\/+|\\/+$/g, '')}`.toLowerCase()\n}\n\nconst HOME_WORDS = /\\bhome(?:\\s?page)?\\b/i\n\n/** Words that ask a visitor to get in touch, which the site's form answers: rule 10's own list (AGL-3729). */\nconst CONTACT_WORDS = { test: aiAsksToGetInTouch }\n\nexport interface AiLayoutLinkScope {\n /** The page's section names in plan order. */\n sections: readonly string[]\n /** The section the link sits in, which it does not scroll to. */\n from: number\n /** The section of this page that places the site's form, if one does. */\n formSection: number | null\n}\n\n/** The page a word names: an id, a label or an address. */\nfunction pageNamed(\n word: string,\n targets: AiLayoutTargets,\n): AiLayoutPage | null {\n const value = word.trim()\n if (!value) return null\n const byId = targets.pages.find((page) => page.id === value)\n if (byId) return byId\n const lower = value.toLowerCase()\n if (lower.startsWith('/'))\n return (\n targets.pages.find((page) => slugOf(page.slug) === slugOf(lower)) ?? null\n )\n const words = wordsOf(value).join(' ')\n return (\n targets.pages.find((page) => wordsOf(page.label).join(' ') === words) ??\n // A page merged into the blog is still named by the title it had (AGL-3676).\n targets.pages.find((page) => page.standsInFor && wordsOf(page.standsInFor).join(' ') === words) ??\n null\n )\n}\n\n/** A destination rule 10 admits, from the page it names, for the words a link shows. */\nfunction pageDestination(\n page: AiLayoutPage | null,\n label: string,\n targets: AiLayoutTargets,\n): AiLayoutDestination | null {\n if (!page || page.id === targets.pageId) return null\n if (\n targets.homeIds.includes(page.id) &&\n !HOME_WORDS.test(label) &&\n !isHomeSlug(page.slug)\n )\n return null\n if (isHomeSlug(page.slug) && !HOME_WORDS.test(label)) return null\n if (page.href) return { kind: 'path', href: page.href, ...(page.standsInFor ? { standsInFor: page.standsInFor, label: page.label } : {}) }\n return { kind: 'page', screenId: page.id }\n}\n\nfunction isHomeSlug(slug: string): boolean {\n return slugOf(slug) === '/'\n}\n\n/** Where the site's form is: a section of this page, else the page that places it. */\nfunction formDestination(\n scope: AiLayoutLinkScope,\n targets: AiLayoutTargets,\n): AiLayoutDestination | null {\n if (scope.formSection !== null && scope.formSection !== scope.from)\n return { kind: 'section', index: scope.formSection }\n if (targets.formPageId && targets.formPageId !== targets.pageId) {\n return { kind: 'page', screenId: targets.formPageId }\n }\n return null\n}\n\n/**\n * Where a link goes: the destination its `to` names, read the language's\n * way; failing that, for words that ask a visitor to get in touch, the site's\n * form; failing that, a page whose name the words say. `null` is a link with\n * nowhere to go, which is left out.\n */\nexport function aiLayoutResolveLink(\n to: string | undefined,\n label: string,\n scope: AiLayoutLinkScope,\n targets: AiLayoutTargets,\n): AiLayoutDestination | null {\n const named = to ? resolveNamed(to.trim(), label, scope, targets) : null\n if (named) return named\n if (CONTACT_WORDS.test(label)) {\n const form = formDestination(scope, targets)\n if (form) return form\n const contact = targets.pages.find(\n (page) =>\n page.id !== targets.pageId &&\n !isHomeSlug(page.slug) &&\n CONTACT_WORDS.test(page.label),\n )\n if (contact) return { kind: 'page', screenId: contact.id }\n }\n const labelWords = new Set(wordsOf(label))\n const said = targets.pages.filter(\n (page) =>\n page.id !== targets.pageId &&\n !isHomeSlug(page.slug) &&\n wordsOf(page.label).length > 0 &&\n wordsOf(page.label).every((word) => labelWords.has(word)),\n )\n return said.length === 1 ? { kind: 'page', screenId: said[0].id } : null\n}\n\nfunction resolveNamed(\n to: string,\n label: string,\n scope: AiLayoutLinkScope,\n targets: AiLayoutTargets,\n): AiLayoutDestination | null {\n const lower = to.toLowerCase()\n if (\n lower === 'form' ||\n lower.startsWith('form:') ||\n targets.forms.some((form) => form.id === to)\n ) {\n return formDestination(scope, targets)\n }\n if (lower.startsWith('https://')) {\n return targets.facts.includes(to) ? { kind: 'href', href: to } : null\n }\n const section = lower.startsWith('#')\n ? to.slice(1)\n : lower.startsWith('section:')\n ? to.slice(8)\n : null\n if (section !== null) {\n const number = Number(section.trim())\n const index =\n Number.isInteger(number) && number >= 1 && number <= scope.sections.length\n ? number - 1\n : aiPageLinkSection(section, scope.sections)\n return index !== null && index !== scope.from\n ? { kind: 'section', index }\n : null\n }\n const page = pageNamed(lower.startsWith('page:') ? to.slice(5) : to, targets)\n return pageDestination(page, label, targets)\n}\n"],"names":["aiAsksToGetInTouch","aiPageLinkSection","aiLayoutRenamedLabel","label","destination","kind","standsInFor","name","toLowerCase","words","wordsOf","length","stems","map","word","replace","pattern","RegExp","stem","join","renamed","found","toUpperCase","text","split","filter","Boolean","slugOf","slug","HOME_WORDS","CONTACT_WORDS","test","pageNamed","targets","value","trim","byId","pages","find","page","id","lower","startsWith","pageDestination","pageId","homeIds","includes","isHomeSlug","href","screenId","formDestination","scope","formSection","from","index","formPageId","aiLayoutResolveLink","to","named","resolveNamed","form","contact","labelWords","Set","said","every","has","forms","some","facts","section","slice","number","Number","isInteger","sections"],"mappings":"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"}
|
|
@@ -110,6 +110,19 @@ export interface AiLayoutListing {
|
|
|
110
110
|
* `fields`: it is the card's picture, not its words.
|
|
111
111
|
*/
|
|
112
112
|
imageField?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Where each record's own page lives (AGL-3727): the base path of the
|
|
115
|
+
* dataset's record template (AGL-3475), without slashes at either end —
|
|
116
|
+
* `services` for `/services/roofing`. Set by code where the plan makes or
|
|
117
|
+
* the site keeps a record template for the dataset, never by the model.
|
|
118
|
+
*/
|
|
119
|
+
recordBase?: string;
|
|
120
|
+
/**
|
|
121
|
+
* The record's page-address field (AGL-3727), which the record's page is
|
|
122
|
+
* served at under {@link recordBase}. Never among `fields`: it is the card's
|
|
123
|
+
* link, not its words.
|
|
124
|
+
*/
|
|
125
|
+
addressField?: string;
|
|
113
126
|
/**
|
|
114
127
|
* Where a visitor goes from a store with nothing listed yet (AGL-3676): the
|
|
115
128
|
* site's contact page, by its path. The empty state names it as its one
|
|
@@ -143,6 +156,13 @@ export declare const AI_LAYOUT_FEATURED_RECORDS: {
|
|
|
143
156
|
readonly signup: 0;
|
|
144
157
|
readonly records: 3;
|
|
145
158
|
};
|
|
159
|
+
/**
|
|
160
|
+
* The link each card of a records listing opens (AGL-3727): the record's own
|
|
161
|
+
* page, `/{base}/{{item.<address field>}}`, which the page fills per record
|
|
162
|
+
* as it repeats the card. `null` where the dataset has no record page, or the
|
|
163
|
+
* listing names no address to reach it by.
|
|
164
|
+
*/
|
|
165
|
+
export declare function aiLayoutRecordHref(listing: Pick<AiLayoutListing, 'kind' | 'recordBase' | 'addressField'>): string | null;
|
|
146
166
|
/** A site's listings as its unit inputs carry them; nothing that does not read is kept. */
|
|
147
167
|
export declare function aiLayoutListingsOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutListing[];
|
|
148
168
|
/** The listing a section of a page places, and how; `null` for a section that places none. */
|
|
@@ -160,6 +180,11 @@ export interface AiLayoutListingScreen {
|
|
|
160
180
|
items: number;
|
|
161
181
|
uses?: readonly string[];
|
|
162
182
|
}>;
|
|
183
|
+
/** The dataset this page is the record template of, and its base (AGL-3475); absent for a page that is one page. */
|
|
184
|
+
record?: {
|
|
185
|
+
dataset: string;
|
|
186
|
+
base: string;
|
|
187
|
+
} | null;
|
|
163
188
|
}
|
|
164
189
|
/**
|
|
165
190
|
* The section of a shop's own page the whole catalog fills (AGL-3676): the
|
|
@@ -196,6 +221,16 @@ export declare function aiLayoutIsShopPage(screen: {
|
|
|
196
221
|
* A section placed once is placed for one listing.
|
|
197
222
|
*/
|
|
198
223
|
export declare function aiLayoutListingPlacements(kind: AiLayoutListingKind, screens: readonly AiLayoutListingScreen[]): AiLayoutListingPlacement[];
|
|
224
|
+
/**
|
|
225
|
+
* Where a dataset's records have pages of their own (AGL-3727): the base of
|
|
226
|
+
* the page the plan makes its record template (AGL-3475), named by its id or
|
|
227
|
+
* as `new:<its name>`; `null` where no page is. Read off the plan, never
|
|
228
|
+
* asked of the model, so every card that lists the dataset links its record.
|
|
229
|
+
*/
|
|
230
|
+
export declare function aiLayoutRecordBaseOf(dataset: {
|
|
231
|
+
id: string;
|
|
232
|
+
name: string;
|
|
233
|
+
}, screens: readonly AiLayoutListingScreen[]): string | null;
|
|
199
234
|
/**
|
|
200
235
|
* The sections that list one of the site's datasets (AGL-3616): every section
|
|
201
236
|
* whose plan names it in its `uses` — `new:<its name>` as the plan wrote it,
|
|
@@ -61,6 +61,18 @@ const text = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
|
61
61
|
/** A path on the site, as a listing's links are kept. */ const SITE_PATH = /^\/[a-z0-9-]+(?:\/[a-z0-9-]+)*$/;
|
|
62
62
|
/** A dataset id and a field id, as the data plugin mints them. */ const DATASET_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
63
63
|
const FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
64
|
+
/** A record template's base: one to four URL-safe segments, as the data plugin keeps it. */ const RECORD_BASE = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*){0,3}$/;
|
|
65
|
+
/**
|
|
66
|
+
* The link each card of a records listing opens (AGL-3727): the record's own
|
|
67
|
+
* page, `/{base}/{{item.<address field>}}`, which the page fills per record
|
|
68
|
+
* as it repeats the card. `null` where the dataset has no record page, or the
|
|
69
|
+
* listing names no address to reach it by.
|
|
70
|
+
*/ export function aiLayoutRecordHref(listing) {
|
|
71
|
+
if (listing.kind !== 'records') return null;
|
|
72
|
+
const base = text(listing.recordBase).replace(/^\/+|\/+$/g, '');
|
|
73
|
+
const field = text(listing.addressField);
|
|
74
|
+
return RECORD_BASE.test(base) && FIELD_ID.test(field) ? `/${base}/{{item.${field}}}` : null;
|
|
75
|
+
}
|
|
64
76
|
/** A records listing's fields as its input carries them; nothing that does not read is kept. */ function listingFieldsOf(raw) {
|
|
65
77
|
if (!Array.isArray(raw)) return [];
|
|
66
78
|
return raw.flatMap((value)=>{
|
|
@@ -88,6 +100,8 @@ const FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
|
88
100
|
const datasetId = kind === 'records' ? text(record['datasetId']) : '';
|
|
89
101
|
const fields = kind === 'records' ? listingFieldsOf(record['fields']) : [];
|
|
90
102
|
const imageField = kind === 'records' ? text(record['imageField']) : '';
|
|
103
|
+
const recordBase = kind === 'records' ? text(record['recordBase']).replace(/^\/+|\/+$/g, '') : '';
|
|
104
|
+
const addressField = kind === 'records' ? text(record['addressField']) : '';
|
|
91
105
|
// Its fields may be left for the page to read off the site's inventory.
|
|
92
106
|
if (kind === 'records' && !DATASET_ID.test(datasetId)) continue;
|
|
93
107
|
if (!KINDS.includes(kind) || listings.some((listing)=>listing.kind === kind && (kind !== 'records' || listing.datasetId === datasetId))) {
|
|
@@ -129,6 +143,10 @@ const FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
|
129
143
|
fields
|
|
130
144
|
} : {}, kind === 'records' && FIELD_ID.test(imageField) ? {
|
|
131
145
|
imageField
|
|
146
|
+
} : {}, kind === 'records' && RECORD_BASE.test(recordBase) ? {
|
|
147
|
+
recordBase
|
|
148
|
+
} : {}, kind === 'records' && RECORD_BASE.test(recordBase) && FIELD_ID.test(addressField) ? {
|
|
149
|
+
addressField
|
|
132
150
|
} : {}, kind === 'products' && record['cart'] === false ? {
|
|
133
151
|
cart: false
|
|
134
152
|
} : {}, kind === 'products' && actionLabel && SITE_PATH.test(actionHref) ? {
|
|
@@ -287,6 +305,22 @@ const isHome = (slug)=>firstSegment(slug) === '';
|
|
|
287
305
|
}
|
|
288
306
|
return placements;
|
|
289
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Where a dataset's records have pages of their own (AGL-3727): the base of
|
|
310
|
+
* the page the plan makes its record template (AGL-3475), named by its id or
|
|
311
|
+
* as `new:<its name>`; `null` where no page is. Read off the plan, never
|
|
312
|
+
* asked of the model, so every card that lists the dataset links its record.
|
|
313
|
+
*/ export function aiLayoutRecordBaseOf(dataset, screens) {
|
|
314
|
+
const named = `new:${dataset.name}`.trim().toLowerCase();
|
|
315
|
+
for (const screen of screens){
|
|
316
|
+
var _screen_record, _screen_record1;
|
|
317
|
+
const ref = text((_screen_record = screen.record) == null ? void 0 : _screen_record.dataset);
|
|
318
|
+
if (!ref || ref !== dataset.id && ref.toLowerCase() !== named) continue;
|
|
319
|
+
const base = text((_screen_record1 = screen.record) == null ? void 0 : _screen_record1.base).replace(/^\/+|\/+$/g, '');
|
|
320
|
+
if (RECORD_BASE.test(base)) return base;
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
290
324
|
/**
|
|
291
325
|
* The sections that list one of the site's datasets (AGL-3616): every section
|
|
292
326
|
* whose plan names it in its `uses` — `new:<its name>` as the plan wrote it,
|