@aglyn/plugins-ai 1.0.0-beta.284 → 1.0.0-beta.287
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/declarations.js +5 -2
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +12 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
- package/src/lib/jobs/ai-build-capabilities.js +27 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +59 -0
- package/src/lib/jobs/ai-image-reads.js +138 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
- package/src/lib/jobs/ai-job-build-step.js +83 -12
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
- package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-import-step.js +15 -11
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
- package/src/lib/jobs/ai-job-insight-step.js +23 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.js +2 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-media-step.js +6 -8
- package/src/lib/jobs/ai-job-media-step.js.map +1 -1
- package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-navigation-step.js +7 -9
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +7 -1
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +16 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +9 -1
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
- package/src/lib/jobs/ai-job-plan-step.js +78 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-privacy-step.js +14 -15
- package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.js +5 -3
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-settings-step.js +7 -8
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
- package/src/lib/jobs/ai-job-site-content.js +67 -8
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
- package/src/lib/jobs/ai-job-site-datasets.js +298 -30
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +6 -3
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
- package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-tracking-step.js +7 -8
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
- package/src/lib/jobs/ai-jobs-beat.js +18 -8
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
- package/src/lib/jobs/ai-jobs-notify.js +136 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +59 -4
- package/src/lib/jobs/ai-layout-stock-photos.js +897 -81
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-product-image.d.ts +14 -0
- package/src/lib/jobs/ai-product-image.js +73 -21
- package/src/lib/jobs/ai-product-image.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
- package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
- package/src/lib/layout-language/ai-layout-compiler.js +194 -18
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
- package/src/lib/layout-language/ai-layout-copy.js +383 -3
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
- package/src/lib/layout-language/ai-layout-design.js +31 -11
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
- package/src/lib/layout-language/ai-layout-listings.js +9 -1
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +30 -0
- package/src/lib/layout-language/ai-layout-pictures.js +131 -3
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
- package/src/lib/layout-language/ai-layout-storefront.js +100 -20
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +7 -0
- package/src/lib/model/ai-build-job.js +36 -7
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +28 -4
- package/src/lib/model/ai-build-plan.js +49 -9
- 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-job-notice.d.ts +8 -0
- package/src/lib/model/ai-job-notice.js +33 -0
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
- package/src/lib/model/ai-job-plan-lines.js +6 -1
- package/src/lib/model/ai-job-plan-lines.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +8 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-permissions.d.ts +13 -0
- package/src/lib/model/ai-permissions.js +29 -1
- package/src/lib/model/ai-permissions.js.map +1 -1
- package/src/lib/model/ai-products.d.ts +18 -0
- package/src/lib/model/ai-products.js +32 -0
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/model/ai-site-inspiration.d.ts +3 -2
- package/src/lib/model/ai-site-inspiration.js +3 -2
- package/src/lib/model/ai-site-inspiration.js.map +1 -1
- package/src/lib/plugin.js +4 -13
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.js +9 -2
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.js +14 -5
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +24 -1
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/routing.js +5 -2
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +99 -1
- package/src/lib/runtime/ai-dataset-generation.js +251 -3
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
- package/src/lib/runtime/ai-dev-replay.js +4 -2
- package/src/lib/runtime/ai-dev-replay.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
- package/src/lib/runtime/ai-doctrine-validators.js +343 -27
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +8 -2
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +27 -1
- package/src/lib/runtime/ai-icon-library.js +30 -3
- package/src/lib/runtime/ai-icon-library.js.map +1 -1
- package/src/lib/runtime/ai-products-generation.d.ts +11 -1
- package/src/lib/runtime/ai-products-generation.js +51 -4
- package/src/lib/runtime/ai-products-generation.js.map +1 -1
- package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
- package/src/lib/runtime/assist-answer-cache.js +5 -2
- package/src/lib/runtime/assist-answer-cache.js.map +1 -1
- package/src/lib/server/ai-allotments.js +7 -2
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-experiment-versions.js +5 -5
- package/src/lib/server/ai-experiment-versions.js.map +1 -1
- package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
- package/src/lib/server/ai-host-permissions-roster.js +33 -0
- package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
- package/src/lib/server/ai-host-permissions.js +8 -6
- package/src/lib/server/ai-host-permissions.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.d.ts +17 -0
- package/src/lib/server/ai-image-reads-route.js +106 -0
- package/src/lib/server/ai-image-reads-route.js.map +1 -0
- package/src/lib/server/ai-seo-apply.js +5 -5
- package/src/lib/server/ai-seo-apply.js.map +1 -1
- package/src/lib/server/ai-site-import-route.js +7 -4
- package/src/lib/server/ai-site-import-route.js.map +1 -1
- package/src/lib/server/assist-build.js +1 -0
- package/src/lib/server/assist-build.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +11 -7
- package/src/lib/server/assist-chat.js +185 -48
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-docs-query.d.ts +51 -0
- package/src/lib/server/assist-docs-query.js +57 -0
- package/src/lib/server/assist-docs-query.js.map +1 -0
- package/src/lib/server/assist-jobs-context.d.ts +102 -0
- package/src/lib/server/assist-jobs-context.js +227 -0
- package/src/lib/server/assist-jobs-context.js.map +1 -0
- package/src/lib/server/assist-view-context.d.ts +8 -4
- package/src/lib/server/assist-view-context.js +12 -7
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +12 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
- package/src/lib/tools/ai-dataset-tool.js +24 -7
- package/src/lib/tools/ai-dataset-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
- package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
- package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-listings.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiStorefrontSays } from './ai-layout-storefront'\n\n/**\n * A SITE'S OWN RECORDS, PLACED BY THE PLATFORM (AGL-3676).\n *\n * A guided start writes a store's first products and a blog's first posts,\n * then built the pages around them in the layout language — and the pages\n * showed neither. \"Featured candles\" and \"The candle range\" on the live\n * Ember & Wick start (2026-10-08) were cards the model wrote, a name and a\n * line each: no photo, no price, no link, nothing a shopper could open; the\n * Clay Notes blog's \"Featured writing\" was three cards that linked to no\n * post. The real records were on the site, one click away from nowhere.\n *\n * A LISTING is one kind of record a site keeps — its catalog, its blog —\n * placed by code in the sections whose plan says they show it, through the\n * element the plugin that keeps the records renders them with, bound to the\n * records themselves rather than to words about them. It is the form binding\n * one step on (a section whose plan `uses` a form places that form by its\n * id, whatever the answer remembered): the model writes the section's\n * heading and words, the platform places the records.\n *\n * - `products` — the commerce plugin's Product grid (`product-grid`), over\n * the site's whole active catalog: a photo, the name, the price (or\n * \"Price coming soon\" where the owner cleared it), each card linking its\n * product's page.\n * - `posts` — the content plugin's Collection Entries (`collectionEntries`)\n * over the blog's collection, its card a cover, the date and byline, the\n * title and the excerpt, linking each post.\n * - `tracks` — the music plugin's Music player (`musicPlayer`, AGL-3716) on a\n * music site, placed EMPTY: its \"add your tracks\" state, for the owner to\n * fill from their own media library. No job ever sources a recording, so\n * a tracks listing has no records and the player no source.\n * - `reviews` — a selling store's own customer reviews, through the commerce\n * plugin's Product reviews in its store-wide scope (`product-reviews`,\n * AGL-3676): approved reviews across the catalog with their star ratings.\n * A store that opens has none, and the element then shows nothing at all\n * on the published page, so no review is ever written for a shopper who\n * did not leave one (the FTC's fake-review rule; rule 14's \"never invent\n * a customer\").\n * - `signup` — a selling store's newsletter sign-up, through the commerce\n * plugin's Newsletter signup (`newsletter-signup`, AGL-3676): a consented\n * email field into the store's contacts, where a section says newsletter.\n * - `records` — one of the site's datasets (AGL-3616): a menu, a team, its\n * services, built by the site start and kept by the data plugin. Drawn as\n * a Box that repeats over the dataset (`repeatDataset`, the platform's own\n * repeat), its card binding the record's fields as `{{item.<field>}}`, in\n * the sections whose plan names the dataset in its `uses`. A site may list\n * several, one listing a dataset.\n * - `events` — the site's dated events (AGL-3755), which a guided start of\n * an event-led business drafts through the events-calendar plugin, listed\n * by its Event List (`eventList`) in the section whose plan names them in\n * its `uses`: each published event with its date, place and cover, and\n * its search-engine Event markup.\n *\n * The elements are named by their persisted ids, as the form binding names\n * `form`: a plugin never imports another's internals, and\n * `ai-layout-listings.spec.ts` holds the ids and props named here to the\n * source of the plugins that declare them.\n *\n * Model only: shapes and the inputs reader; the compiler draws them.\n */\n\nexport type AiLayoutListingKind = 'products' | 'posts' | 'tracks' | 'reviews' | 'signup' | 'records' | 'events'\n\n/** How a section shows a listing: a few of its records on a page about something else, or all of them on its own page. */\nexport type AiLayoutListingRole = 'featured' | 'index'\n\n/** One section of one page that places a listing. */\nexport interface AiLayoutListingPlacement {\n /** The page, by its plan id. */\n screenId: string\n /** The section, by plan index. */\n section: number\n role: AiLayoutListingRole\n /**\n * The one group of a dataset's records the section lists (AGL-3921), where\n * the page lists the dataset in several sections each named for one value\n * of its category-like field: \"Family portrait gallery\" lists the records\n * filed \"Family portraits\", drawn as the repeat's `repeatFilter`.\n */\n filter?: AiLayoutListingFilter\n}\n\n/** A records placement's group: the field, its value, and the records filed under it, by name. */\nexport interface AiLayoutListingFilter {\n field: string\n value: string\n records: string[]\n}\n\n/** One field of a dataset a `records` listing shows, as its card binds it. */\nexport interface AiLayoutListingField {\n /** The field's id, which `{{item.<id>}}` reads. */\n id: string\n name: string\n /** The data plugin's stored type: `text`, `float`, `int32`, `bool`, `sorted`. */\n type: string\n}\n\n/** A kind of record the site keeps, and where its pages place it. */\nexport interface AiLayoutListing {\n /** `listing:products`, `listing:posts`: what a section's placement names. */\n id: string\n kind: AiLayoutListingKind\n /** What a visitor would call the whole list: \"the shop\", \"the blog\". */\n name: string\n /** The records it shows today, by name, so a page's words can name them. */\n records: string[]\n /** Where the whole list lives, as a path on the site: the blog's `/blog`. */\n href?: string\n /** The content collection a posts listing repeats, by its slug. */\n collectionSlug?: string\n /** The dataset a records listing repeats over, by id (AGL-3616). */\n datasetId?: string\n /** The dataset's fields, in its order, which the card binds. */\n fields?: AiLayoutListingField[]\n /**\n * The field holding each record's photo (AGL-3616), which the card leads\n * with as its Image's `src`, so a portfolio stays a gallery. Never among\n * `fields`: it is the card's picture, not its words.\n */\n imageField?: string\n /**\n * Where each record's own page lives (AGL-3727): the base path of the\n * dataset's record template (AGL-3475), without slashes at either end —\n * `services` for `/services/roofing`. Set by code where the plan makes or\n * the site keeps a record template for the dataset, never by the model.\n */\n recordBase?: string\n /**\n * The record's page-address field (AGL-3727), which the record's page is\n * served at under {@link recordBase}. Never among `fields`: it is the card's\n * link, not its words.\n */\n addressField?: string\n /**\n * Where a visitor goes from a store with nothing listed yet (AGL-3676): the\n * site's contact page, by its path. The empty state names it as its one\n * action, so a shop that opens empty still answers a visitor.\n */\n emptyAction?: { label: string; href: string }\n /**\n * `false` for a store that lists its catalog but cannot sell yet (its first\n * products were skipped or failed, AGL-3676): the header carries no cart.\n */\n cart?: boolean\n placements: AiLayoutListingPlacement[]\n}\n\n/** The unit input a site's listings travel in, to its layout and its pages. */\nexport const AI_LAYOUT_LISTINGS_INPUT = 'siteListings'\n\n/** The id a listing of a kind is placed by; a records listing is one dataset's. */\nexport function aiLayoutListingId(kind: AiLayoutListingKind, datasetId?: string): string {\n return kind === 'records' && datasetId ? `listing:records:${datasetId}` : `listing:${kind}`\n}\n\n/** The persisted element a listing is drawn with, by kind (held to its plugin's source by a spec). */\nexport const AI_LAYOUT_LISTING_ELEMENTS: Readonly<Record<AiLayoutListingKind, string>> = {\n products: 'product-grid',\n posts: 'collectionEntries',\n tracks: 'musicPlayer',\n reviews: 'product-reviews',\n signup: 'newsletter-signup',\n // The platform's own Box, repeating its card over the dataset.\n records: 'muiBox',\n events: 'eventList',\n}\n\n/** The header's cart button: the commerce plugin's Cart in its `button` variant. */\nexport const AI_LAYOUT_CART_ELEMENT = 'cart'\n\n/** The most records a featured band shows; a page of its own shows them all. */\nexport const AI_LAYOUT_FEATURED_RECORDS = { products: 4, posts: 3, tracks: 0, reviews: 3, signup: 0, records: 3, events: 3 } as const\n\nconst PLACEMENT_ROLES: readonly AiLayoutListingRole[] = ['featured', 'index']\nconst KINDS: readonly AiLayoutListingKind[] = ['products', 'posts', 'tracks', 'reviews', 'signup', 'records', 'events']\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What a visitor calls each kind's whole list, where a unit's input names none. */\nconst LISTING_NAMES: Readonly<Record<AiLayoutListingKind, string>> = {\n products: 'the shop',\n posts: 'the blog',\n tracks: 'the music',\n reviews: 'the store',\n signup: 'the newsletter',\n records: 'the list',\n events: 'the events',\n}\n\n/** A path on the site, as a listing's links are kept. */\nconst SITE_PATH = /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/\n\n/** A dataset id and a field id, as the data plugin mints them. */\nconst DATASET_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/\n\n/** A record template's base: one to four URL-safe segments, as the data plugin keeps it. */\nconst RECORD_BASE = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\\/[a-z0-9]+(?:-[a-z0-9]+)*){0,3}$/\n\n/**\n * The link each card of a records listing opens (AGL-3727): the record's own\n * page, `/{base}/{{item.<address field>}}`, which the page fills per record\n * as it repeats the card. `null` where the dataset has no record page, or the\n * listing names no address to reach it by.\n */\nexport function aiLayoutRecordHref(listing: Pick<AiLayoutListing, 'kind' | 'recordBase' | 'addressField'>): string | null {\n if (listing.kind !== 'records') return null\n const base = text(listing.recordBase).replace(/^\\/+|\\/+$/g, '')\n const field = text(listing.addressField)\n return RECORD_BASE.test(base) && FIELD_ID.test(field) ? `/${base}/{{item.${field}}}` : null\n}\n\n/** A records listing's fields as its input carries them; nothing that does not read is kept. */\nfunction listingFieldsOf(raw: unknown): AiLayoutListingField[] {\n if (!Array.isArray(raw)) return []\n return raw\n .flatMap((value) => {\n const field = (value ?? {}) as Record<string, unknown>\n const id = text(field['id'])\n return FIELD_ID.test(id) ? [{ id, name: text(field['name']).slice(0, 60) || id, type: text(field['type']) || 'text' }] : []\n })\n .slice(0, 16)\n}\n\n/** A placement's group as its input carries it; one whose field or value does not read is none. */\nfunction listingFilterOf(raw: unknown): AiLayoutListingFilter | null {\n const filter = (raw ?? {}) as Record<string, unknown>\n const field = text(filter['field'])\n // The repeat's filter is `field == value` on one line, at most 200 characters.\n const value = text(filter['value']).replace(/\\s+/g, ' ').slice(0, 120)\n if (!FIELD_ID.test(field) || !value) return null\n const records = (Array.isArray(filter['records']) ? filter['records'] : []).map(text).filter(Boolean).slice(0, 12)\n return { field, value, records }\n}\n\n/** A site's listings as its unit inputs carry them; nothing that does not read is kept. */\nexport function aiLayoutListingsOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutListing[] {\n const raw = inputs?.[AI_LAYOUT_LISTINGS_INPUT]\n if (!Array.isArray(raw)) return []\n const listings: AiLayoutListing[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const record = entry as Record<string, unknown>\n const kind = record['kind'] as AiLayoutListingKind\n // A records listing is one dataset's (AGL-3616): a site may list several.\n const datasetId = kind === 'records' ? text(record['datasetId']) : ''\n const fields = kind === 'records' ? listingFieldsOf(record['fields']) : []\n const imageField = kind === 'records' ? text(record['imageField']) : ''\n const recordBase = kind === 'records' ? text(record['recordBase']).replace(/^\\/+|\\/+$/g, '') : ''\n const addressField = kind === 'records' ? text(record['addressField']) : ''\n // Its fields may be left for the page to read off the site's inventory.\n if (kind === 'records' && !DATASET_ID.test(datasetId)) continue\n if (\n !KINDS.includes(kind) ||\n listings.some((listing) => listing.kind === kind && (kind !== 'records' || listing.datasetId === datasetId))\n ) {\n continue\n }\n const href = text(record['href'])\n const action = (record['emptyAction'] ?? {}) as Record<string, unknown>\n const actionLabel = text(action['label']).slice(0, 40)\n const actionHref = text(action['href'])\n const collectionSlug = text(record['collectionSlug'])\n // A posts listing with no collection to repeat shows nothing.\n if (kind === 'posts' && !/^[a-z0-9-]{1,100}$/.test(collectionSlug)) continue\n const placements = (Array.isArray(record['placements']) ? record['placements'] : []).flatMap((value) => {\n const placement = (value ?? {}) as Record<string, unknown>\n const screenId = text(placement['screenId'])\n const section = placement['section']\n const role = placement['role'] as AiLayoutListingRole\n const filter = kind === 'records' ? listingFilterOf(placement['filter']) : null\n return screenId && typeof section === 'number' && Number.isInteger(section) && section >= 0 && PLACEMENT_ROLES.includes(role)\n ? [{ screenId, section, role, ...(filter ? { filter } : {}) }]\n : []\n })\n listings.push({\n id: aiLayoutListingId(kind, datasetId),\n kind,\n name: text(record['name']) || LISTING_NAMES[kind],\n // A tracks listing names no records: no job sources a recording (AGL-3716);\n // nor do a store's reviews and its sign-up, which shoppers fill (AGL-3676).\n records:\n kind !== 'products' && kind !== 'posts' && kind !== 'records' && kind !== 'events'\n ? []\n : (Array.isArray(record['records']) ? record['records'] : []).map(text).filter(Boolean).slice(0, 12),\n ...(SITE_PATH.test(href) ? { href } : {}),\n ...(kind === 'posts' ? { collectionSlug } : {}),\n ...(kind === 'records' ? { datasetId, fields } : {}),\n ...(kind === 'records' && FIELD_ID.test(imageField) ? { imageField } : {}),\n // A record page's base, and the address field where the unit knew it; a\n // build's page reads the field off the site's inventory (AGL-3727).\n ...(kind === 'records' && RECORD_BASE.test(recordBase) ? { recordBase } : {}),\n ...(kind === 'records' && RECORD_BASE.test(recordBase) && FIELD_ID.test(addressField) ? { addressField } : {}),\n ...(kind === 'products' && record['cart'] === false ? { cart: false } : {}),\n ...(kind === 'products' && actionLabel && SITE_PATH.test(actionHref) ? { emptyAction: { label: actionLabel, href: actionHref } } : {}),\n placements,\n })\n }\n return listings\n}\n\n/** The listing a section of a page places, and how; `null` for a section that places none. */\nexport function aiLayoutListingAt(\n listings: readonly AiLayoutListing[],\n screenId: string | null | undefined,\n section: number,\n): { listing: AiLayoutListing; role: AiLayoutListingRole; filter?: AiLayoutListingFilter } | null {\n if (!screenId) return null\n for (const listing of listings) {\n const placement = listing.placements.find((entry) => entry.screenId === screenId && entry.section === section)\n if (placement) return { listing, role: placement.role, ...(placement.filter ? { filter: placement.filter } : {}) }\n }\n return null\n}\n\n/** The repeat filter a placement's group is drawn as: `category == Family portraits`. */\nexport function aiLayoutRepeatFilterOf(filter: Pick<AiLayoutListingFilter, 'field' | 'value'>): string {\n return `${filter.field} == ${filter.value}`\n}\n\n// ── Where a site's pages place them ────────────────────────────────────\n\n/** The plan of a page, as a placement is chosen from it. */\nexport interface AiLayoutListingScreen {\n id?: string | null\n title: string\n slug: string\n sections: ReadonlyArray<{ name: string; items: number; uses?: readonly string[] }>\n /** The dataset this page is the record template of, and its base (AGL-3475); absent for a page that is one page. */\n record?: { dataset: string; base: string } | null\n}\n\nconst PRODUCT_PAGE = /\\b(shop|store|products?|catalog(?:ue)?|collections?|range|browse)\\b/i\nconst PRODUCT_SECTION =\n /\\b(shop|store|products?|catalog(?:ue)?|collections?|range|bestsellers?|best[- ]sellers?|new arrivals|arrivals|featured|favou?rites|signature)\\b/i\nconst POST_SECTION = /\\b(blog|posts?|articles?|writing|journal|stories|essays?|latest|recent|news|featured)\\b/i\n/** A music site's page or section about its recordings (AGL-3716). */\nconst MUSIC_PAGE = /\\b(music|listen|tracks?|songs?|releases?|discography|albums?|eps?|singles?|sounds?)\\b/i\n\n/**\n * The section of a shop's own page the whole catalog fills (AGL-3676): the\n * first after its opening whose name says products (\"Product grid\", \"The\n * range\"), else the section planned with the most items, else its second.\n * The live Hearth & Wick start (2026-10-09) planned \"Product range image\n * cards\" — six cards naming kinds of candle, with no product, price or cart.\n */\nexport function aiLayoutShopGridSection(sections: ReadonlyArray<{ name: string; items: number }>): number {\n const named = sections.findIndex((section, index) => index > 0 && PRODUCT_SECTION.test(section.name) && !SHOP_FRAMING.test(section.name))\n if (named !== -1) return named\n const most = Math.max(0, ...sections.map((section) => section.items))\n const byItems = most > 0 ? sections.findIndex((section) => section.items === most) : -1\n return byItems !== -1 ? byItems : sections.length > 1 ? 1 : 0\n}\n\n/**\n * The section of a store's home its bestsellers fill (AGL-3676): the first\n * after the hero whose name says them (\"Bestsellers\", \"Featured candles\", \"New\n * arrivals\"), never one about the store's collections, its reviews or its\n * sign-up; else the first after the hero naming products at all; else the\n * first after the hero planned with items that is none of those. The\n * beta.237 Willow Wick home would otherwise have listed products in its\n * \"Shop by collection\" tiles.\n */\nfunction aiLayoutFeaturedSection(screen: AiLayoutListingScreen): number {\n // A section about the collections that does not also name the bestsellers.\n const other = (name: string) =>\n (aiStorefrontSays('collections', name) && !aiStorefrontSays('featured', name)) ||\n aiStorefrontSays('reviews', name) ||\n aiStorefrontSays('signup', name)\n const first = (test: (name: string, items: number) => boolean) =>\n screen.sections.findIndex((section, index) => index > 0 && test(section.name, section.items))\n for (const test of [\n (name: string) => aiStorefrontSays('featured', name) && !other(name),\n (name: string) => PRODUCT_SECTION.test(name) && !other(name),\n (name: string, items: number) => items > 0 && !other(name),\n (name: string) => PRODUCT_SECTION.test(name),\n ]) {\n const found = first(test)\n if (found !== -1) return found\n }\n return -1\n}\n\n/** A section of a shop page that only opens or closes it: \"Shop intro heading\", \"Gift help call to action\". */\nconst SHOP_FRAMING = /\\b(hero|intro(?:duction)?|heading|banner|cta|call to action|contact)\\b/i\n\n/** Whether a planned page is the store's own Shop page: its address or its name says it sells. */\nexport function aiLayoutIsShopPage(screen: { slug: string; title: string }): boolean {\n return !isHome(screen.slug) && PRODUCT_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`)\n}\n\nconst firstSegment = (slug: string) => slug.trim().replace(/^\\/+/, '').split('/')[0].toLowerCase()\nconst isHome = (slug: string) => firstSegment(slug) === ''\n\n/**\n * The sections of a site's pages that show a listing, chosen by code from the\n * plan (AGL-3676), never asked of the model:\n *\n * - PRODUCTS. The shop's own page — the page whose address or name says it\n * sells (`/shop`, \"Shop\", \"Products\") — shows the whole catalog in the\n * section the plan gave the most items, else its second; and the home\n * page features a few in its first section after the hero whose name\n * says products (\"Featured candles\", \"Bestsellers\"), else in the first\n * section after the hero planned with items.\n * - POSTS. The home features the latest in its first section of items after\n * the hero whose name says writing (\"Featured writing\", \"Latest posts\"),\n * else its first section of items after the hero; and any other page\n * whose name says it is about the writing does the same. The blog's own\n * index is the platform's.\n *\n * A section placed once is placed for one listing.\n */\nexport function aiLayoutListingPlacements(\n kind: AiLayoutListingKind,\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const placements: AiLayoutListingPlacement[] = []\n const place = (screen: AiLayoutListingScreen, section: number, role: AiLayoutListingRole) => {\n if (typeof screen.id === 'string' && screen.id && section >= 0) placements.push({ screenId: screen.id, section, role })\n }\n const afterHero = (screen: AiLayoutListingScreen, test: (name: string, items: number) => boolean) =>\n screen.sections.findIndex((section, index) => index > 0 && test(section.name, section.items))\n if (kind === 'tracks') {\n // A music site's player (AGL-3716): on its Music page, in the section that\n // says it is about the recordings, else the one after the opening; on the\n // home, only in a section named for them. A site with neither gets it on\n // its home, after the opening.\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n place(screen, afterHero(screen, (name) => MUSIC_PAGE.test(name)), 'featured')\n continue\n }\n if (!MUSIC_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`)) continue\n const named = afterHero(screen, (name) => MUSIC_PAGE.test(name))\n place(screen, named !== -1 ? named : screen.sections.length > 1 ? 1 : 0, 'index')\n }\n if (!placements.length) {\n const home = screens.find((screen) => isHome(screen.slug))\n if (home) place(home, home.sections.length > 1 ? 1 : 0, 'featured')\n }\n return placements\n }\n if (kind === 'reviews' || kind === 'signup') {\n // A store's own reviews, on its home, in the section named for them; its\n // newsletter sign-up in any page's section named for it that places no\n // form of the plan's (AGL-3676). Neither is ever a page's opening.\n for (const screen of screens) {\n if (kind === 'reviews' && !isHome(screen.slug)) continue\n place(\n screen,\n afterHero(\n screen,\n (name) => aiStorefrontSays(kind, name),\n ),\n kind === 'reviews' ? 'featured' : 'index',\n )\n }\n return kind === 'signup'\n ? placements.filter((placement) => {\n const screen = screens.find((entry) => entry.id === placement.screenId)\n return !(screen?.sections[placement.section]?.uses ?? []).some((use) => /form/i.test(use))\n })\n : placements\n }\n if (kind === 'products') {\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n place(screen, aiLayoutFeaturedSection(screen), 'featured')\n continue\n }\n if (!aiLayoutIsShopPage(screen)) continue\n place(screen, aiLayoutShopGridSection(screen.sections), 'index')\n }\n return placements\n }\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n // On a blog's home the band that names the writing (\"Featured posts\n // cards\", \"Featured journeys\") lists it; failing one, the first band of\n // items after the hero. A live Clay Notes plan (2026-10-09) opened its\n // home with \"What you will find\" before \"Featured posts cards\".\n const named = afterHero(screen, (name, items) => items > 0 && POST_SECTION.test(name))\n place(screen, named !== -1 ? named : afterHero(screen, (_name, items) => items > 0), 'featured')\n continue\n }\n if (!POST_SECTION.test(screen.title)) continue\n place(screen, afterHero(screen, (name) => POST_SECTION.test(name)), 'featured')\n }\n return placements\n}\n\n/**\n * Where a dataset's records have pages of their own (AGL-3727): the base of\n * the page the plan makes its record template (AGL-3475), named by its id or\n * as `new:<its name>`; `null` where no page is. Read off the plan, never\n * asked of the model, so every card that lists the dataset links its record.\n */\nexport function aiLayoutRecordBaseOf(\n dataset: { id: string; name: string },\n screens: readonly AiLayoutListingScreen[],\n): string | null {\n const named = `new:${dataset.name}`.trim().toLowerCase()\n for (const screen of screens) {\n const ref = text(screen.record?.dataset)\n if (!ref || (ref !== dataset.id && ref.toLowerCase() !== named)) continue\n const base = text(screen.record?.base).replace(/^\\/+|\\/+$/g, '')\n if (RECORD_BASE.test(base)) return base\n }\n return null\n}\n\n/**\n * The sections that list one of the site's datasets (AGL-3616): every section\n * whose plan names it in its `uses` — `new:<its name>` as the plan wrote it,\n * or its id once built. The home page features a few of its records; any\n * other page lists them all. Chosen from the plan, never asked of the model.\n */\nexport function aiLayoutRecordsPlacements(\n dataset: {\n id: string\n name: string\n /** Its records, by name, as {@link groups} files them. */\n records?: readonly string[]\n /** What its records are filed under: the field, by id, and each record's value, by position. */\n groups?: { field: string; values: readonly string[] } | null\n },\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const named = `new:${dataset.name}`.trim().toLowerCase()\n const placements: AiLayoutListingPlacement[] = []\n for (const screen of screens) {\n if (typeof screen.id !== 'string' || !screen.id) continue\n const here: AiLayoutListingPlacement[] = []\n screen.sections.forEach((section, index) => {\n const lists = (section.uses ?? []).some((ref) => ref === dataset.id || ref.trim().toLowerCase() === named)\n if (lists) here.push({ screenId: screen.id as string, section: index, role: isHome(screen.slug) ? 'featured' : 'index' })\n })\n placements.push(...aiLayoutGroupedPlacements(here, screen, dataset))\n }\n return placements\n}\n\n/** Words a section's name adds to a group's without naming another: \"Family portrait gallery\" is \"Family portraits\". */\nconst GROUP_FRAMING = new Set(\n 'a an and the of our my your for with in at to by from all more recent selected featured favorite gallery galleries portfolio collection collections work works photos photo photographs pictures images showcase grid list cards section'.split(' '),\n)\n\n/** A name's words as a group is matched by: lowercase, a plural's \"-s\" dropped, framing kept. */\nfunction groupWords(name: string): string[] {\n return name\n .toLowerCase()\n .split(/[^a-z0-9À-ɏ]+/)\n .filter(Boolean)\n .map((word) => (word.length > 3 && word.endsWith('ies') ? `${word.slice(0, -3)}y` : word.length > 3 && /[^s]s$/.test(word) ? word.slice(0, -1) : word))\n}\n\n/**\n * The value of a dataset's groups a section's name is named for (AGL-3921),\n * or `null`: every word of the value is in the name (\"Weddings and\n * elopements gallery\" is \"Weddings and elopements\"), else every word of the\n * name past its framing is in the value (\"Weddings\" is \"Weddings and\n * elopements\"), and no other value is named as well. Pure.\n */\nexport function aiLayoutSectionGroup(sectionName: string, values: readonly string[]): string | null {\n const said = groupWords(sectionName)\n const own = said.filter((word) => !GROUP_FRAMING.has(word))\n const distinct = [...new Map(values.filter((value) => value.trim()).map((value) => [value.trim().toLowerCase(), value.trim()])).values()]\n const wordsOf = (value: string) => groupWords(value).filter((word) => !GROUP_FRAMING.has(word))\n const found = distinct.filter((value) => {\n const words = wordsOf(value)\n return (words.length > 0 && words.every((word) => said.includes(word))) || (own.length > 0 && own.every((word) => words.includes(word)))\n })\n if (found.length !== 1) return null\n const value = found[0] as string\n // \"Weddings and family portraits\" names a second group in part: it lists both.\n const others = distinct.filter((other) => other !== value).flatMap(wordsOf)\n const mine = wordsOf(value)\n return own.some((word) => others.includes(word) && !mine.includes(word)) ? null : value\n}\n\n/**\n * A page's placements of one dataset, each section named for one of its\n * groups listing only that group's records (AGL-3921). The beta.265 Laurel\n * Ridge \"Galleries\" page planned \"Weddings and elopements gallery\" and\n * \"Family portrait gallery\" over one \"Gallery collections\" whose records were\n * filed under those two categories, and both sections listed all ten: the\n * plan names no filter, so the build reads it off the sections' names. Only\n * where the page lists the dataset more than once; a section named for no\n * one group lists them all. Pure.\n */\nexport function aiLayoutGroupedPlacements(\n placements: readonly AiLayoutListingPlacement[],\n screen: Pick<AiLayoutListingScreen, 'sections'>,\n dataset: { records?: readonly string[]; groups?: { field: string; values: readonly string[] } | null },\n): AiLayoutListingPlacement[] {\n const groups = dataset.groups\n if (placements.length < 2 || !groups || !FIELD_ID.test(groups.field)) return [...placements]\n return placements.map((placement) => {\n const value = aiLayoutSectionGroup(screen.sections[placement.section]?.name ?? '', groups.values)\n if (!value) return placement\n const records = (dataset.records ?? []).filter((_name, index) => (groups.values[index] ?? '').trim().toLowerCase() === value.toLowerCase())\n return { ...placement, filter: { field: groups.field, value, records } }\n })\n}\n\n/**\n * The sections that list the site's events (AGL-3755): every section whose\n * plan names one of them in its `uses` (`new:<its name>`), once however many\n * it names. The home page features the next few; any other page lists them\n * all. Chosen from the plan, never asked of the model.\n */\nexport function aiLayoutEventsPlacements(\n names: readonly string[],\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const named = new Set(names.map((name) => `new:${name}`.trim().toLowerCase()))\n const placements: AiLayoutListingPlacement[] = []\n for (const screen of screens) {\n if (typeof screen.id !== 'string' || !screen.id) continue\n screen.sections.forEach((section, index) => {\n if ((section.uses ?? []).some((ref) => named.has(ref.trim().toLowerCase()))) {\n placements.push({ screenId: screen.id as string, section: index, role: isHome(screen.slug) ? 'featured' : 'index' })\n }\n })\n }\n return placements\n}\n\n/**\n * A field whose name says it holds what a record's photo shows (AGL-3856):\n * the alt text of the record's Image, never words of its card.\n */\nconst RECORD_IMAGE_ALT = /^(?:image|photo|picture) (?:description|alt(?: text)?)$/i\n\n/** Whether a field's name says it holds what a record's photo shows (AGL-3856). */\nexport function aiLayoutIsRecordImageAlt(name: string): boolean {\n return RECORD_IMAGE_ALT.test(name.trim())\n}\n\n/** The field holding what a record's photo shows, by id, or `null` (AGL-3856). */\nexport function aiLayoutRecordImageAltField(fields: readonly { id: string; name: string }[]): string | null {\n return fields.find((field) => aiLayoutIsRecordImageAlt(field.name))?.id ?? null\n}\n"],"names":["aiStorefrontSays","AI_LAYOUT_LISTINGS_INPUT","aiLayoutListingId","kind","datasetId","AI_LAYOUT_LISTING_ELEMENTS","products","posts","tracks","reviews","signup","records","events","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_FEATURED_RECORDS","PLACEMENT_ROLES","KINDS","text","value","trim","LISTING_NAMES","SITE_PATH","DATASET_ID","FIELD_ID","RECORD_BASE","aiLayoutRecordHref","listing","base","recordBase","replace","field","addressField","test","listingFieldsOf","raw","Array","isArray","flatMap","id","name","slice","type","listingFilterOf","filter","map","Boolean","aiLayoutListingsOf","inputs","listings","entry","record","fields","imageField","includes","some","href","action","actionLabel","actionHref","collectionSlug","placements","placement","screenId","section","role","Number","isInteger","push","cart","emptyAction","label","aiLayoutListingAt","find","aiLayoutRepeatFilterOf","PRODUCT_PAGE","PRODUCT_SECTION","POST_SECTION","MUSIC_PAGE","aiLayoutShopGridSection","sections","named","findIndex","index","SHOP_FRAMING","most","Math","max","items","byItems","length","aiLayoutFeaturedSection","screen","other","first","found","aiLayoutIsShopPage","isHome","slug","firstSegment","title","split","toLowerCase","aiLayoutListingPlacements","screens","place","afterHero","home","uses","use","_name","aiLayoutRecordBaseOf","dataset","ref","aiLayoutRecordsPlacements","here","forEach","lists","aiLayoutGroupedPlacements","GROUP_FRAMING","Set","groupWords","word","endsWith","aiLayoutSectionGroup","sectionName","values","said","own","has","distinct","Map","wordsOf","words","every","others","mine","groups","aiLayoutEventsPlacements","names","RECORD_IMAGE_ALT","aiLayoutIsRecordImageAlt","aiLayoutRecordImageAltField"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-listings.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiStorefrontSays } from './ai-layout-storefront'\n\n/**\n * A SITE'S OWN RECORDS, PLACED BY THE PLATFORM (AGL-3676).\n *\n * A guided start writes a store's first products and a blog's first posts,\n * then built the pages around them in the layout language — and the pages\n * showed neither. \"Featured candles\" and \"The candle range\" on the live\n * Ember & Wick start (2026-10-08) were cards the model wrote, a name and a\n * line each: no photo, no price, no link, nothing a shopper could open; the\n * Clay Notes blog's \"Featured writing\" was three cards that linked to no\n * post. The real records were on the site, one click away from nowhere.\n *\n * A LISTING is one kind of record a site keeps — its catalog, its blog —\n * placed by code in the sections whose plan says they show it, through the\n * element the plugin that keeps the records renders them with, bound to the\n * records themselves rather than to words about them. It is the form binding\n * one step on (a section whose plan `uses` a form places that form by its\n * id, whatever the answer remembered): the model writes the section's\n * heading and words, the platform places the records.\n *\n * - `products` — the commerce plugin's Product grid (`product-grid`), over\n * the site's whole active catalog: a photo, the name, the price (or\n * \"Price coming soon\" where the owner cleared it), each card linking its\n * product's page.\n * - `posts` — the content plugin's Collection Entries (`collectionEntries`)\n * over the blog's collection, its card a cover, the date and byline, the\n * title and the excerpt, linking each post.\n * - `tracks` — the music plugin's Music player (`musicPlayer`, AGL-3716) on a\n * music site, placed EMPTY: its \"add your tracks\" state, for the owner to\n * fill from their own media library. No job ever sources a recording, so\n * a tracks listing has no records and the player no source.\n * - `reviews` — a selling store's own customer reviews, through the commerce\n * plugin's Product reviews in its store-wide scope (`product-reviews`,\n * AGL-3676): approved reviews across the catalog with their star ratings.\n * A store that opens has none, and the element then shows nothing at all\n * on the published page, so no review is ever written for a shopper who\n * did not leave one (the FTC's fake-review rule; rule 14's \"never invent\n * a customer\").\n * - `signup` — a selling store's newsletter sign-up, through the commerce\n * plugin's Newsletter signup (`newsletter-signup`, AGL-3676): a consented\n * email field into the store's contacts, where a section says newsletter.\n * - `records` — one of the site's datasets (AGL-3616): a menu, a team, its\n * services, built by the site start and kept by the data plugin. Drawn as\n * a Box that repeats over the dataset (`repeatDataset`, the platform's own\n * repeat), its card binding the record's fields as `{{item.<field>}}`, in\n * the sections whose plan names the dataset in its `uses`. A site may list\n * several, one listing a dataset.\n * - `events` — the site's dated events (AGL-3755), which a guided start of\n * an event-led business drafts through the events-calendar plugin, listed\n * by its Event List (`eventList`) in the section whose plan names them in\n * its `uses`: each published event with its date, place and cover, and\n * its search-engine Event markup.\n *\n * The elements are named by their persisted ids, as the form binding names\n * `form`: a plugin never imports another's internals, and\n * `ai-layout-listings.spec.ts` holds the ids and props named here to the\n * source of the plugins that declare them.\n *\n * Model only: shapes and the inputs reader; the compiler draws them.\n */\n\nexport type AiLayoutListingKind = 'products' | 'posts' | 'tracks' | 'reviews' | 'signup' | 'records' | 'events'\n\n/** How a section shows a listing: a few of its records on a page about something else, or all of them on its own page. */\nexport type AiLayoutListingRole = 'featured' | 'index'\n\n/** One section of one page that places a listing. */\nexport interface AiLayoutListingPlacement {\n /** The page, by its plan id. */\n screenId: string\n /** The section, by plan index. */\n section: number\n role: AiLayoutListingRole\n /**\n * The one group of a dataset's records the section lists (AGL-3921), where\n * the page lists the dataset in several sections each named for one value\n * of its category-like field: \"Family portrait gallery\" lists the records\n * filed \"Family portraits\", drawn as the repeat's `repeatFilter`.\n */\n filter?: AiLayoutListingFilter\n}\n\n/** A records placement's group: the field, its value, and the records filed under it, by name. */\nexport interface AiLayoutListingFilter {\n field: string\n value: string\n records: string[]\n}\n\n/** One field of a dataset a `records` listing shows, as its card binds it. */\nexport interface AiLayoutListingField {\n /** The field's id, which `{{item.<id>}}` reads. */\n id: string\n name: string\n /** The data plugin's stored type: `text`, `float`, `int32`, `bool`, `sorted`. */\n type: string\n}\n\n/** A kind of record the site keeps, and where its pages place it. */\nexport interface AiLayoutListing {\n /** `listing:products`, `listing:posts`: what a section's placement names. */\n id: string\n kind: AiLayoutListingKind\n /** What a visitor would call the whole list: \"the shop\", \"the blog\". */\n name: string\n /** The records it shows today, by name, so a page's words can name them. */\n records: string[]\n /** Where the whole list lives, as a path on the site: the blog's `/blog`. */\n href?: string\n /** The content collection a posts listing repeats, by its slug. */\n collectionSlug?: string\n /** The dataset a records listing repeats over, by id (AGL-3616). */\n datasetId?: string\n /** The dataset's fields, in its order, which the card binds. */\n fields?: AiLayoutListingField[]\n /**\n * The field holding each record's photo (AGL-3616), which the card leads\n * with as its Image's `src`, so a portfolio stays a gallery. Never among\n * `fields`: it is the card's picture, not its words.\n */\n imageField?: string\n /**\n * Where each record's own page lives (AGL-3727): the base path of the\n * dataset's record template (AGL-3475), without slashes at either end —\n * `services` for `/services/roofing`. Set by code where the plan makes or\n * the site keeps a record template for the dataset, never by the model.\n */\n recordBase?: string\n /**\n * The record's page-address field (AGL-3727), which the record's page is\n * served at under {@link recordBase}. Never among `fields`: it is the card's\n * link, not its words.\n */\n addressField?: string\n /**\n * Where a visitor goes from a store with nothing listed yet (AGL-3676): the\n * site's contact page, by its path. The empty state names it as its one\n * action, so a shop that opens empty still answers a visitor.\n */\n emptyAction?: { label: string; href: string }\n /**\n * `false` for a store that lists its catalog but cannot sell yet (its first\n * products were skipped or failed, AGL-3676): the header carries no cart.\n */\n cart?: boolean\n placements: AiLayoutListingPlacement[]\n}\n\n/** The unit input a site's listings travel in, to its layout and its pages. */\nexport const AI_LAYOUT_LISTINGS_INPUT = 'siteListings'\n\n/** The id a listing of a kind is placed by; a records listing is one dataset's. */\nexport function aiLayoutListingId(kind: AiLayoutListingKind, datasetId?: string): string {\n return kind === 'records' && datasetId ? `listing:records:${datasetId}` : `listing:${kind}`\n}\n\n/** The persisted element a listing is drawn with, by kind (held to its plugin's source by a spec). */\nexport const AI_LAYOUT_LISTING_ELEMENTS: Readonly<Record<AiLayoutListingKind, string>> = {\n products: 'product-grid',\n posts: 'collectionEntries',\n tracks: 'musicPlayer',\n reviews: 'product-reviews',\n signup: 'newsletter-signup',\n // The platform's own Box, repeating its card over the dataset.\n records: 'muiBox',\n events: 'eventList',\n}\n\n/** The header's cart button: the commerce plugin's Cart in its `button` variant. */\nexport const AI_LAYOUT_CART_ELEMENT = 'cart'\n\n/** The most records a featured band shows; a page of its own shows them all. */\nexport const AI_LAYOUT_FEATURED_RECORDS = { products: 4, posts: 3, tracks: 0, reviews: 3, signup: 0, records: 3, events: 3 } as const\n\nconst PLACEMENT_ROLES: readonly AiLayoutListingRole[] = ['featured', 'index']\nconst KINDS: readonly AiLayoutListingKind[] = ['products', 'posts', 'tracks', 'reviews', 'signup', 'records', 'events']\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What a visitor calls each kind's whole list, where a unit's input names none. */\nconst LISTING_NAMES: Readonly<Record<AiLayoutListingKind, string>> = {\n products: 'the shop',\n posts: 'the blog',\n tracks: 'the music',\n reviews: 'the store',\n signup: 'the newsletter',\n records: 'the list',\n events: 'the events',\n}\n\n/** A path on the site, as a listing's links are kept. */\nconst SITE_PATH = /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/\n\n/** A dataset id and a field id, as the data plugin mints them. */\nconst DATASET_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst FIELD_ID = /^[A-Za-z][A-Za-z0-9_]{0,63}$/\n\n/** A record template's base: one to four URL-safe segments, as the data plugin keeps it. */\nconst RECORD_BASE = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\\/[a-z0-9]+(?:-[a-z0-9]+)*){0,3}$/\n\n/**\n * The link each card of a records listing opens (AGL-3727): the record's own\n * page, `/{base}/{{item.<address field>}}`, which the page fills per record\n * as it repeats the card. `null` where the dataset has no record page, or the\n * listing names no address to reach it by.\n */\nexport function aiLayoutRecordHref(listing: Pick<AiLayoutListing, 'kind' | 'recordBase' | 'addressField'>): string | null {\n if (listing.kind !== 'records') return null\n const base = text(listing.recordBase).replace(/^\\/+|\\/+$/g, '')\n const field = text(listing.addressField)\n return RECORD_BASE.test(base) && FIELD_ID.test(field) ? `/${base}/{{item.${field}}}` : null\n}\n\n/** A records listing's fields as its input carries them; nothing that does not read is kept. */\nfunction listingFieldsOf(raw: unknown): AiLayoutListingField[] {\n if (!Array.isArray(raw)) return []\n return raw\n .flatMap((value) => {\n const field = (value ?? {}) as Record<string, unknown>\n const id = text(field['id'])\n return FIELD_ID.test(id) ? [{ id, name: text(field['name']).slice(0, 60) || id, type: text(field['type']) || 'text' }] : []\n })\n .slice(0, 16)\n}\n\n/** A placement's group as its input carries it; one whose field or value does not read is none. */\nfunction listingFilterOf(raw: unknown): AiLayoutListingFilter | null {\n const filter = (raw ?? {}) as Record<string, unknown>\n const field = text(filter['field'])\n // The repeat's filter is `field == value` on one line, at most 200 characters.\n const value = text(filter['value']).replace(/\\s+/g, ' ').slice(0, 120)\n if (!FIELD_ID.test(field) || !value) return null\n const records = (Array.isArray(filter['records']) ? filter['records'] : []).map(text).filter(Boolean).slice(0, 12)\n return { field, value, records }\n}\n\n/** A site's listings as its unit inputs carry them; nothing that does not read is kept. */\nexport function aiLayoutListingsOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutListing[] {\n const raw = inputs?.[AI_LAYOUT_LISTINGS_INPUT]\n if (!Array.isArray(raw)) return []\n const listings: AiLayoutListing[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const record = entry as Record<string, unknown>\n const kind = record['kind'] as AiLayoutListingKind\n // A records listing is one dataset's (AGL-3616): a site may list several.\n const datasetId = kind === 'records' ? text(record['datasetId']) : ''\n const fields = kind === 'records' ? listingFieldsOf(record['fields']) : []\n const imageField = kind === 'records' ? text(record['imageField']) : ''\n const recordBase = kind === 'records' ? text(record['recordBase']).replace(/^\\/+|\\/+$/g, '') : ''\n const addressField = kind === 'records' ? text(record['addressField']) : ''\n // Its fields may be left for the page to read off the site's inventory.\n if (kind === 'records' && !DATASET_ID.test(datasetId)) continue\n if (\n !KINDS.includes(kind) ||\n listings.some((listing) => listing.kind === kind && (kind !== 'records' || listing.datasetId === datasetId))\n ) {\n continue\n }\n const href = text(record['href'])\n const action = (record['emptyAction'] ?? {}) as Record<string, unknown>\n const actionLabel = text(action['label']).slice(0, 40)\n const actionHref = text(action['href'])\n const collectionSlug = text(record['collectionSlug'])\n // A posts listing with no collection to repeat shows nothing.\n if (kind === 'posts' && !/^[a-z0-9-]{1,100}$/.test(collectionSlug)) continue\n const placements = (Array.isArray(record['placements']) ? record['placements'] : []).flatMap((value) => {\n const placement = (value ?? {}) as Record<string, unknown>\n const screenId = text(placement['screenId'])\n const section = placement['section']\n const role = placement['role'] as AiLayoutListingRole\n const filter = kind === 'records' ? listingFilterOf(placement['filter']) : null\n return screenId && typeof section === 'number' && Number.isInteger(section) && section >= 0 && PLACEMENT_ROLES.includes(role)\n ? [{ screenId, section, role, ...(filter ? { filter } : {}) }]\n : []\n })\n listings.push({\n id: aiLayoutListingId(kind, datasetId),\n kind,\n name: text(record['name']) || LISTING_NAMES[kind],\n // A tracks listing names no records: no job sources a recording (AGL-3716);\n // nor do a store's reviews and its sign-up, which shoppers fill (AGL-3676).\n records:\n kind !== 'products' && kind !== 'posts' && kind !== 'records' && kind !== 'events'\n ? []\n : (Array.isArray(record['records']) ? record['records'] : []).map(text).filter(Boolean).slice(0, 12),\n ...(SITE_PATH.test(href) ? { href } : {}),\n ...(kind === 'posts' ? { collectionSlug } : {}),\n ...(kind === 'records' ? { datasetId, fields } : {}),\n ...(kind === 'records' && FIELD_ID.test(imageField) ? { imageField } : {}),\n // A record page's base, and the address field where the unit knew it; a\n // build's page reads the field off the site's inventory (AGL-3727).\n ...(kind === 'records' && RECORD_BASE.test(recordBase) ? { recordBase } : {}),\n ...(kind === 'records' && RECORD_BASE.test(recordBase) && FIELD_ID.test(addressField) ? { addressField } : {}),\n ...(kind === 'products' && record['cart'] === false ? { cart: false } : {}),\n ...(kind === 'products' && actionLabel && SITE_PATH.test(actionHref) ? { emptyAction: { label: actionLabel, href: actionHref } } : {}),\n placements,\n })\n }\n return listings\n}\n\n/** The listing a section of a page places, and how; `null` for a section that places none. */\nexport function aiLayoutListingAt(\n listings: readonly AiLayoutListing[],\n screenId: string | null | undefined,\n section: number,\n): { listing: AiLayoutListing; role: AiLayoutListingRole; filter?: AiLayoutListingFilter } | null {\n if (!screenId) return null\n for (const listing of listings) {\n const placement = listing.placements.find((entry) => entry.screenId === screenId && entry.section === section)\n if (placement) return { listing, role: placement.role, ...(placement.filter ? { filter: placement.filter } : {}) }\n }\n return null\n}\n\n/** The repeat filter a placement's group is drawn as: `category == Family portraits`. */\nexport function aiLayoutRepeatFilterOf(filter: Pick<AiLayoutListingFilter, 'field' | 'value'>): string {\n return `${filter.field} == ${filter.value}`\n}\n\n// ── Where a site's pages place them ────────────────────────────────────\n\n/** The plan of a page, as a placement is chosen from it. */\nexport interface AiLayoutListingScreen {\n id?: string | null\n title: string\n slug: string\n sections: ReadonlyArray<{ name: string; items: number; uses?: readonly string[] }>\n /** The dataset this page is the record template of, and its base (AGL-3475); absent for a page that is one page. */\n record?: { dataset: string; base: string } | null\n}\n\nconst PRODUCT_PAGE = /\\b(shop|store|products?|catalog(?:ue)?|collections?|range|browse)\\b/i\nconst PRODUCT_SECTION =\n /\\b(shop|store|products?|catalog(?:ue)?|collections?|range|bestsellers?|best[- ]sellers?|new arrivals|arrivals|featured|favou?rites|signature)\\b/i\nconst POST_SECTION = /\\b(blog|posts?|articles?|writing|journal|stories|essays?|latest|recent|news|featured)\\b/i\n/** A music site's page or section about its recordings (AGL-3716). */\nconst MUSIC_PAGE = /\\b(music|listen|tracks?|songs?|releases?|discography|albums?|eps?|singles?|sounds?)\\b/i\n\n/**\n * The section of a shop's own page the whole catalog fills (AGL-3676): the\n * first after its opening whose name says products (\"Product grid\", \"The\n * range\"), else the section planned with the most items, else its second.\n * The live Hearth & Wick start (2026-10-09) planned \"Product range image\n * cards\" — six cards naming kinds of candle, with no product, price or cart.\n */\nexport function aiLayoutShopGridSection(sections: ReadonlyArray<{ name: string; items: number }>): number {\n const named = sections.findIndex((section, index) => index > 0 && PRODUCT_SECTION.test(section.name) && !SHOP_FRAMING.test(section.name))\n if (named !== -1) return named\n const most = Math.max(0, ...sections.map((section) => section.items))\n const byItems = most > 0 ? sections.findIndex((section) => section.items === most) : -1\n return byItems !== -1 ? byItems : sections.length > 1 ? 1 : 0\n}\n\n/**\n * The section of a store's home its bestsellers fill (AGL-3676): the first\n * after the hero whose name says them (\"Bestsellers\", \"Featured candles\", \"New\n * arrivals\"), never one about the store's collections, its reviews or its\n * sign-up; else the first after the hero naming products at all; else the\n * first after the hero planned with items that is none of those. The\n * beta.237 Willow Wick home would otherwise have listed products in its\n * \"Shop by collection\" tiles.\n */\nfunction aiLayoutFeaturedSection(screen: AiLayoutListingScreen): number {\n // A section about the collections that does not also name the bestsellers.\n const other = (name: string) =>\n (aiStorefrontSays('collections', name) && !aiStorefrontSays('featured', name)) ||\n aiStorefrontSays('reviews', name) ||\n aiStorefrontSays('signup', name)\n const first = (test: (name: string, items: number) => boolean) =>\n screen.sections.findIndex((section, index) => index > 0 && test(section.name, section.items))\n for (const test of [\n (name: string) => aiStorefrontSays('featured', name) && !other(name),\n (name: string) => PRODUCT_SECTION.test(name) && !other(name),\n (name: string, items: number) => items > 0 && !other(name),\n (name: string) => PRODUCT_SECTION.test(name),\n ]) {\n const found = first(test)\n if (found !== -1) return found\n }\n return -1\n}\n\n/** A section of a shop page that only opens or closes it: \"Shop intro heading\", \"Gift help call to action\". */\nconst SHOP_FRAMING = /\\b(hero|intro(?:duction)?|heading|banner|cta|call to action|contact)\\b/i\n\n/** Whether a planned page is the store's own Shop page: its address or its name says it sells. */\nexport function aiLayoutIsShopPage(screen: { slug: string; title: string }): boolean {\n return !isHome(screen.slug) && PRODUCT_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`)\n}\n\nconst firstSegment = (slug: string) => slug.trim().replace(/^\\/+/, '').split('/')[0].toLowerCase()\nconst isHome = (slug: string) => firstSegment(slug) === ''\n\n/**\n * The sections of a site's pages that show a listing, chosen by code from the\n * plan (AGL-3676), never asked of the model:\n *\n * - PRODUCTS. The shop's own page — the page whose address or name says it\n * sells (`/shop`, \"Shop\", \"Products\") — shows the whole catalog in the\n * section the plan gave the most items, else its second; and the home\n * page features a few in its first section after the hero whose name\n * says products (\"Featured candles\", \"Bestsellers\"), else in the first\n * section after the hero planned with items.\n * - POSTS. The home features the latest in its first section of items after\n * the hero whose name says writing (\"Featured writing\", \"Latest posts\"),\n * else its first section of items after the hero; and any other page\n * whose name says it is about the writing does the same. The blog's own\n * index is the platform's.\n *\n * A section placed once is placed for one listing.\n */\nexport function aiLayoutListingPlacements(\n kind: AiLayoutListingKind,\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const placements: AiLayoutListingPlacement[] = []\n const place = (screen: AiLayoutListingScreen, section: number, role: AiLayoutListingRole) => {\n if (typeof screen.id === 'string' && screen.id && section >= 0) placements.push({ screenId: screen.id, section, role })\n }\n const afterHero = (screen: AiLayoutListingScreen, test: (name: string, items: number) => boolean) =>\n screen.sections.findIndex((section, index) => index > 0 && test(section.name, section.items))\n if (kind === 'tracks') {\n // A music site's player (AGL-3716): on its Music page, in the section that\n // says it is about the recordings, else the one after the opening; on the\n // home, only in a section named for them. A site with neither gets it on\n // its home, after the opening.\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n place(screen, afterHero(screen, (name) => MUSIC_PAGE.test(name)), 'featured')\n continue\n }\n if (!MUSIC_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`)) continue\n const named = afterHero(screen, (name) => MUSIC_PAGE.test(name))\n place(screen, named !== -1 ? named : screen.sections.length > 1 ? 1 : 0, 'index')\n }\n if (!placements.length) {\n const home = screens.find((screen) => isHome(screen.slug))\n if (home) place(home, home.sections.length > 1 ? 1 : 0, 'featured')\n }\n return placements\n }\n if (kind === 'reviews' || kind === 'signup') {\n // A store's own reviews, on its home, in the section named for them; its\n // newsletter sign-up in any page's section named for it that places no\n // form of the plan's (AGL-3676). Neither is ever a page's opening.\n for (const screen of screens) {\n if (kind === 'reviews' && !isHome(screen.slug)) continue\n place(\n screen,\n afterHero(\n screen,\n (name) => aiStorefrontSays(kind, name),\n ),\n kind === 'reviews' ? 'featured' : 'index',\n )\n }\n return kind === 'signup'\n ? placements.filter((placement) => {\n const screen = screens.find((entry) => entry.id === placement.screenId)\n return !(screen?.sections[placement.section]?.uses ?? []).some((use) => /form/i.test(use))\n })\n : placements\n }\n if (kind === 'products') {\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n place(screen, aiLayoutFeaturedSection(screen), 'featured')\n continue\n }\n if (!aiLayoutIsShopPage(screen)) continue\n place(screen, aiLayoutShopGridSection(screen.sections), 'index')\n }\n return placements\n }\n for (const screen of screens) {\n if (isHome(screen.slug)) {\n // On a blog's home the band that names the writing (\"Featured posts\n // cards\", \"Featured journeys\") lists it; failing one, the first band of\n // items after the hero. A live Clay Notes plan (2026-10-09) opened its\n // home with \"What you will find\" before \"Featured posts cards\".\n const named = afterHero(screen, (name, items) => items > 0 && POST_SECTION.test(name))\n place(screen, named !== -1 ? named : afterHero(screen, (_name, items) => items > 0), 'featured')\n continue\n }\n if (!POST_SECTION.test(screen.title)) continue\n place(screen, afterHero(screen, (name) => POST_SECTION.test(name)), 'featured')\n }\n return placements\n}\n\n/**\n * Where a dataset's records have pages of their own (AGL-3727): the base of\n * the page the plan makes its record template (AGL-3475), named by its id or\n * as `new:<its name>`; `null` where no page is. Read off the plan, never\n * asked of the model, so every card that lists the dataset links its record.\n */\nexport function aiLayoutRecordBaseOf(\n dataset: { id: string; name: string },\n screens: readonly AiLayoutListingScreen[],\n): string | null {\n const named = `new:${dataset.name}`.trim().toLowerCase()\n for (const screen of screens) {\n const ref = text(screen.record?.dataset)\n if (!ref || (ref !== dataset.id && ref.toLowerCase() !== named)) continue\n const base = text(screen.record?.base).replace(/^\\/+|\\/+$/g, '')\n if (RECORD_BASE.test(base)) return base\n }\n return null\n}\n\n/**\n * The sections that list one of the site's datasets (AGL-3616): every section\n * whose plan names it in its `uses` — `new:<its name>` as the plan wrote it,\n * or its id once built. The home page features a few of its records; any\n * other page lists them all. Chosen from the plan, never asked of the model.\n */\nexport function aiLayoutRecordsPlacements(\n dataset: {\n id: string\n name: string\n /** Its records, by name, as {@link groups} files them. */\n records?: readonly string[]\n /**\n * What its records are filed under: the field, by id, each record's\n * value, by position, and the values its sections list where they were\n * planned for them (AGL-4065).\n */\n groups?: { field: string; values: readonly string[]; expected?: readonly string[] } | null\n },\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const named = `new:${dataset.name}`.trim().toLowerCase()\n const placements: AiLayoutListingPlacement[] = []\n for (const screen of screens) {\n if (typeof screen.id !== 'string' || !screen.id) continue\n const here: AiLayoutListingPlacement[] = []\n screen.sections.forEach((section, index) => {\n const lists = (section.uses ?? []).some((ref) => ref === dataset.id || ref.trim().toLowerCase() === named)\n if (lists) here.push({ screenId: screen.id as string, section: index, role: isHome(screen.slug) ? 'featured' : 'index' })\n })\n placements.push(...aiLayoutGroupedPlacements(here, screen, dataset))\n }\n return placements\n}\n\n/** Words a section's name adds to a group's without naming another: \"Family portrait gallery\" is \"Family portraits\". */\nconst GROUP_FRAMING = new Set(\n 'a an and the of our my your for with in at to by from all more recent selected featured favorite gallery galleries portfolio collection collections work works photos photo photographs pictures images showcase grid list cards section'.split(' '),\n)\n\n/** A name's words as a group is matched by: lowercase, a plural's \"-s\" dropped, framing kept. */\nfunction groupWords(name: string): string[] {\n return name\n .toLowerCase()\n .split(/[^a-z0-9À-ɏ]+/)\n .filter(Boolean)\n .map((word) => (word.length > 3 && word.endsWith('ies') ? `${word.slice(0, -3)}y` : word.length > 3 && /[^s]s$/.test(word) ? word.slice(0, -1) : word))\n}\n\n/**\n * The value of a dataset's groups a section's name is named for (AGL-3921),\n * or `null`: every word of the value is in the name (\"Weddings and\n * elopements gallery\" is \"Weddings and elopements\"), else every word of the\n * name past its framing is in the value (\"Weddings\" is \"Weddings and\n * elopements\"), and no other value is named as well. Pure.\n */\nexport function aiLayoutSectionGroup(sectionName: string, values: readonly string[]): string | null {\n const said = groupWords(sectionName)\n const own = said.filter((word) => !GROUP_FRAMING.has(word))\n const distinct = [...new Map(values.filter((value) => value.trim()).map((value) => [value.trim().toLowerCase(), value.trim()])).values()]\n const wordsOf = (value: string) => groupWords(value).filter((word) => !GROUP_FRAMING.has(word))\n const found = distinct.filter((value) => {\n const words = wordsOf(value)\n return (words.length > 0 && words.every((word) => said.includes(word))) || (own.length > 0 && own.every((word) => words.includes(word)))\n })\n if (found.length !== 1) return null\n const value = found[0] as string\n // \"Weddings and family portraits\" names a second group in part: it lists both.\n const others = distinct.filter((other) => other !== value).flatMap(wordsOf)\n const mine = wordsOf(value)\n return own.some((word) => others.includes(word) && !mine.includes(word)) ? null : value\n}\n\n/**\n * A page's placements of one dataset, each section named for one of its\n * groups listing only that group's records (AGL-3921). The beta.265 Laurel\n * Ridge \"Galleries\" page planned \"Weddings and elopements gallery\" and\n * \"Family portrait gallery\" over one \"Gallery collections\" whose records were\n * filed under those two categories, and both sections listed all ten: the\n * plan names no filter, so the build reads it off the sections' names. Only\n * where the page lists the dataset more than once; a section named for no\n * one group lists them all. Pure.\n */\nexport function aiLayoutGroupedPlacements(\n placements: readonly AiLayoutListingPlacement[],\n screen: Pick<AiLayoutListingScreen, 'sections'>,\n dataset: {\n records?: readonly string[]\n groups?: { field: string; values: readonly string[]; expected?: readonly string[] } | null\n },\n): AiLayoutListingPlacement[] {\n const groups = dataset.groups\n if (placements.length < 2 || !groups || !FIELD_ID.test(groups.field)) return [...placements]\n // The values the sections were planned to list come first (AGL-4065): an\n // empty list's sections are filtered too, so the owner's first rows land\n // in the right one.\n const values = [...(groups.expected ?? []), ...groups.values]\n return placements.map((placement) => {\n const value = aiLayoutSectionGroup(screen.sections[placement.section]?.name ?? '', values)\n if (!value) return placement\n const records = (dataset.records ?? []).filter((_name, index) => (groups.values[index] ?? '').trim().toLowerCase() === value.toLowerCase())\n return { ...placement, filter: { field: groups.field, value, records } }\n })\n}\n\n/**\n * The sections that list the site's events (AGL-3755): every section whose\n * plan names one of them in its `uses` (`new:<its name>`), once however many\n * it names. The home page features the next few; any other page lists them\n * all. Chosen from the plan, never asked of the model.\n */\nexport function aiLayoutEventsPlacements(\n names: readonly string[],\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListingPlacement[] {\n const named = new Set(names.map((name) => `new:${name}`.trim().toLowerCase()))\n const placements: AiLayoutListingPlacement[] = []\n for (const screen of screens) {\n if (typeof screen.id !== 'string' || !screen.id) continue\n screen.sections.forEach((section, index) => {\n if ((section.uses ?? []).some((ref) => named.has(ref.trim().toLowerCase()))) {\n placements.push({ screenId: screen.id as string, section: index, role: isHome(screen.slug) ? 'featured' : 'index' })\n }\n })\n }\n return placements\n}\n\n/**\n * A field whose name says it holds what a record's photo shows (AGL-3856):\n * the alt text of the record's Image, never words of its card.\n */\nconst RECORD_IMAGE_ALT = /^(?:image|photo|picture) (?:description|alt(?: text)?)$/i\n\n/** Whether a field's name says it holds what a record's photo shows (AGL-3856). */\nexport function aiLayoutIsRecordImageAlt(name: string): boolean {\n return RECORD_IMAGE_ALT.test(name.trim())\n}\n\n/** The field holding what a record's photo shows, by id, or `null` (AGL-3856). */\nexport function aiLayoutRecordImageAltField(fields: readonly { id: string; name: string }[]): string | null {\n return fields.find((field) => aiLayoutIsRecordImageAlt(field.name))?.id ?? null\n}\n"],"names":["aiStorefrontSays","AI_LAYOUT_LISTINGS_INPUT","aiLayoutListingId","kind","datasetId","AI_LAYOUT_LISTING_ELEMENTS","products","posts","tracks","reviews","signup","records","events","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_FEATURED_RECORDS","PLACEMENT_ROLES","KINDS","text","value","trim","LISTING_NAMES","SITE_PATH","DATASET_ID","FIELD_ID","RECORD_BASE","aiLayoutRecordHref","listing","base","recordBase","replace","field","addressField","test","listingFieldsOf","raw","Array","isArray","flatMap","id","name","slice","type","listingFilterOf","filter","map","Boolean","aiLayoutListingsOf","inputs","listings","entry","record","fields","imageField","includes","some","href","action","actionLabel","actionHref","collectionSlug","placements","placement","screenId","section","role","Number","isInteger","push","cart","emptyAction","label","aiLayoutListingAt","find","aiLayoutRepeatFilterOf","PRODUCT_PAGE","PRODUCT_SECTION","POST_SECTION","MUSIC_PAGE","aiLayoutShopGridSection","sections","named","findIndex","index","SHOP_FRAMING","most","Math","max","items","byItems","length","aiLayoutFeaturedSection","screen","other","first","found","aiLayoutIsShopPage","isHome","slug","firstSegment","title","split","toLowerCase","aiLayoutListingPlacements","screens","place","afterHero","home","uses","use","_name","aiLayoutRecordBaseOf","dataset","ref","aiLayoutRecordsPlacements","here","forEach","lists","aiLayoutGroupedPlacements","GROUP_FRAMING","Set","groupWords","word","endsWith","aiLayoutSectionGroup","sectionName","values","said","own","has","distinct","Map","wordsOf","words","every","others","mine","groups","expected","aiLayoutEventsPlacements","names","RECORD_IMAGE_ALT","aiLayoutIsRecordImageAlt","aiLayoutRecordImageAltField"],"mappings":"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"}
|
|
@@ -61,6 +61,14 @@ import { type AiSiteStarterSlot, type AiStockRefusedPicks } from '../model/ai-si
|
|
|
61
61
|
* still pinned to the site's dark scheme so the words written for a dark
|
|
62
62
|
* photo stay readable, and a split's words take the row's width.
|
|
63
63
|
*
|
|
64
|
+
* A gallery picture the library found nothing fitting for takes one of the
|
|
65
|
+
* job's own photos first, one the page does not show yet (AGL-4065), and a
|
|
66
|
+
* band of pictures left with none at all is left out of the page, never
|
|
67
|
+
* shown as a heading over captions ({@link aiLayoutDropEmptyPictureBands}):
|
|
68
|
+
* the 2026-10-25 Rut Runners Racing home shipped "The team at the track in
|
|
69
|
+
* pictures" as six captions and no picture, while its job had copied
|
|
70
|
+
* thirteen motocross photos.
|
|
71
|
+
*
|
|
64
72
|
* A slot keeps the alt text the model wrote only when it describes the photo
|
|
65
73
|
* placed: a stock photo that is not what it says is described by what it is
|
|
66
74
|
* (`aiStockPhotoAlt`), and a starter photo always by its own words, since
|
|
@@ -267,6 +275,28 @@ export declare function aiLayoutPlacePictures(nodes: NodesMap, slots: readonly A
|
|
|
267
275
|
* starter's included, is kept.
|
|
268
276
|
*/
|
|
269
277
|
export declare function aiLayoutDropEmptyPictures(nodes: NodesMap, slots: readonly AiLayoutPictureSlot[], photos: ReadonlyArray<AiLayoutPicturePhoto | null>, sectionIds?: readonly string[]): NodesMap;
|
|
278
|
+
/**
|
|
279
|
+
* Which slots are cells of a band of pictures (AGL-4065): two or more of one
|
|
280
|
+
* section's pictures, each in a cell of the same grid, as the compiler draws
|
|
281
|
+
* a group of pictures over their captions. Pure.
|
|
282
|
+
*/
|
|
283
|
+
export declare function aiLayoutPictureBandCells(nodes: NodesMap, slots: readonly AiLayoutPictureSlot[]): boolean[];
|
|
284
|
+
/**
|
|
285
|
+
* The page without its bands of pictures that show no picture (AGL-4065): a
|
|
286
|
+
* section, never the first, whose own name says it is pictures, or whose
|
|
287
|
+
* every picture was a cell of one grid, and which holds nothing but words
|
|
288
|
+
* once its empty pictures are gone. It is left out whole: never a heading
|
|
289
|
+
* over captions. The 2026-10-25 Rut Runners Racing and Pit Row Tacos homes
|
|
290
|
+
* shipped "The team at the track in pictures" and "The trailer and the food
|
|
291
|
+
* in pictures" as six captions each. A band holding a photo, a record's
|
|
292
|
+
* picture, a form or anything else that is not words is kept.
|
|
293
|
+
*/
|
|
294
|
+
export declare function aiLayoutDropEmptyPictureBands(nodes: NodesMap, input: {
|
|
295
|
+
sectionIds: readonly string[];
|
|
296
|
+
sectionNames: readonly string[];
|
|
297
|
+
slots?: readonly AiLayoutPictureSlot[];
|
|
298
|
+
photos?: ReadonlyArray<AiLayoutPicturePhoto | null>;
|
|
299
|
+
}): NodesMap;
|
|
270
300
|
/**
|
|
271
301
|
* A source of photos found for the slots themselves — a stock search copied
|
|
272
302
|
* into the site's library — answering per slot, in order: a photo; or
|
|
@@ -506,6 +506,109 @@ function rotate(list, by) {
|
|
|
506
506
|
}
|
|
507
507
|
return next;
|
|
508
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* A section's name that says it is a band of pictures (AGL-4065): "The team
|
|
511
|
+
* at the track in pictures", "Photo gallery", "Photos from race day".
|
|
512
|
+
*/ const PICTURE_BAND = /\b(?:in|through)\s+(?:pictures|photos|photographs|images)\b|\b(?:photo|picture|image)\s+galler(?:y|ies)\b|\bgalleries?\b|^\W*(?:photos|pictures|photographs|snapshots)\b/i;
|
|
513
|
+
/** Components that only hold words, or lay them out: what a band of captions is made of. */ const WORDS_ONLY = new Set([
|
|
514
|
+
'section',
|
|
515
|
+
'div',
|
|
516
|
+
'muiContainer',
|
|
517
|
+
'muiStack',
|
|
518
|
+
'muiGrid',
|
|
519
|
+
'muiBox',
|
|
520
|
+
'muiTypography',
|
|
521
|
+
'muiListItemText',
|
|
522
|
+
'muiList',
|
|
523
|
+
'muiListItem',
|
|
524
|
+
'muiDivider',
|
|
525
|
+
'muiButton',
|
|
526
|
+
'icon'
|
|
527
|
+
]);
|
|
528
|
+
/**
|
|
529
|
+
* Which slots are cells of a band of pictures (AGL-4065): two or more of one
|
|
530
|
+
* section's pictures, each in a cell of the same grid, as the compiler draws
|
|
531
|
+
* a group of pictures over their captions. Pure.
|
|
532
|
+
*/ export function aiLayoutPictureBandCells(nodes, slots) {
|
|
533
|
+
var _ref;
|
|
534
|
+
const map = nodes;
|
|
535
|
+
const parent = new Map();
|
|
536
|
+
for (const [id, node] of Object.entries(map))for (const child of (_ref = node == null ? void 0 : node.nodes) != null ? _ref : [])parent.set(child, id);
|
|
537
|
+
/** The grid a picture sits in a cell of: the nearest container above it. */ const gridOf = (id)=>{
|
|
538
|
+
var _map_at, _map_at_props, _map_at1;
|
|
539
|
+
let at = parent.get(id);
|
|
540
|
+
while(at && !(((_map_at = map[at]) == null ? void 0 : _map_at.componentId) === 'muiGrid' && ((_map_at1 = map[at]) == null ? void 0 : (_map_at_props = _map_at1.props) == null ? void 0 : _map_at_props['container']) === true))at = parent.get(at);
|
|
541
|
+
return at != null ? at : null;
|
|
542
|
+
};
|
|
543
|
+
const grids = slots.map((slot)=>{
|
|
544
|
+
var _slot_frameId;
|
|
545
|
+
return slot.sectionIndex > 0 ? gridOf((_slot_frameId = slot.frameId) != null ? _slot_frameId : slot.imageId) : null;
|
|
546
|
+
});
|
|
547
|
+
return slots.map((slot, index)=>{
|
|
548
|
+
const grid = grids[index];
|
|
549
|
+
return Boolean(grid) && slots.filter((other, at)=>other.sectionIndex === slot.sectionIndex && grids[at] === grid).length >= 2;
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* The page without its bands of pictures that show no picture (AGL-4065): a
|
|
554
|
+
* section, never the first, whose own name says it is pictures, or whose
|
|
555
|
+
* every picture was a cell of one grid, and which holds nothing but words
|
|
556
|
+
* once its empty pictures are gone. It is left out whole: never a heading
|
|
557
|
+
* over captions. The 2026-10-25 Rut Runners Racing and Pit Row Tacos homes
|
|
558
|
+
* shipped "The team at the track in pictures" and "The trailer and the food
|
|
559
|
+
* in pictures" as six captions each. A band holding a photo, a record's
|
|
560
|
+
* picture, a form or anything else that is not words is kept.
|
|
561
|
+
*/ export function aiLayoutDropEmptyPictureBands(nodes, input) {
|
|
562
|
+
var _input_slots, _input_photos;
|
|
563
|
+
const map = nodes;
|
|
564
|
+
const children = (id)=>{
|
|
565
|
+
var _ref;
|
|
566
|
+
var _map_id;
|
|
567
|
+
return (_ref = (_map_id = map[id]) == null ? void 0 : _map_id['nodes']) != null ? _ref : [];
|
|
568
|
+
};
|
|
569
|
+
const within = (id)=>[
|
|
570
|
+
id,
|
|
571
|
+
...children(id).flatMap(within)
|
|
572
|
+
];
|
|
573
|
+
const slots = (_input_slots = input.slots) != null ? _input_slots : [];
|
|
574
|
+
const photos = (_input_photos = input.photos) != null ? _input_photos : [];
|
|
575
|
+
const parentOf = (id)=>{
|
|
576
|
+
var _Object_keys_find;
|
|
577
|
+
return (_Object_keys_find = Object.keys(map).find((key)=>children(key).includes(id))) != null ? _Object_keys_find : null;
|
|
578
|
+
};
|
|
579
|
+
const cells = aiLayoutPictureBandCells(nodes, slots);
|
|
580
|
+
const dropped = input.sectionIds.filter((sectionId, index)=>{
|
|
581
|
+
var _input_sectionNames_index;
|
|
582
|
+
if (index === 0 || !map[sectionId]) return false;
|
|
583
|
+
// Every picture it had was a cell of its band, and none is left with a photo.
|
|
584
|
+
const own = slots.map((slot, at)=>({
|
|
585
|
+
slot,
|
|
586
|
+
at
|
|
587
|
+
})).filter(({ slot })=>slot.sectionIndex === index);
|
|
588
|
+
const pictured = own.length > 0 && own.every(({ at })=>{
|
|
589
|
+
var _photos_at;
|
|
590
|
+
return cells[at] && !((_photos_at = photos[at]) == null ? void 0 : _photos_at.src);
|
|
591
|
+
});
|
|
592
|
+
if (!pictured && !PICTURE_BAND.test((_input_sectionNames_index = input.sectionNames[index]) != null ? _input_sectionNames_index : '')) return false;
|
|
593
|
+
return within(sectionId).every((id)=>{
|
|
594
|
+
var _ref;
|
|
595
|
+
var _map_id;
|
|
596
|
+
return WORDS_ONLY.has(String((_ref = (_map_id = map[id]) == null ? void 0 : _map_id['componentId']) != null ? _ref : ''));
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
if (!dropped.length) return nodes;
|
|
600
|
+
const next = _extends({}, map);
|
|
601
|
+
for (const sectionId of dropped){
|
|
602
|
+
var _ref;
|
|
603
|
+
var _next_parent;
|
|
604
|
+
const parent = parentOf(sectionId);
|
|
605
|
+
if (parent) next[parent] = _extends({}, next[parent], {
|
|
606
|
+
nodes: ((_ref = (_next_parent = next[parent]) == null ? void 0 : _next_parent['nodes']) != null ? _ref : []).filter((child)=>child !== sectionId)
|
|
607
|
+
});
|
|
608
|
+
for (const id of within(sectionId))delete next[id];
|
|
609
|
+
}
|
|
610
|
+
return next;
|
|
611
|
+
}
|
|
509
612
|
/**
|
|
510
613
|
* Fills a stored page's empty picture slots (AGL-3660): from `source` where
|
|
511
614
|
* one is given and it answers; a slot it answers with nothing fitting stays
|
|
@@ -527,7 +630,8 @@ function rotate(list, by) {
|
|
|
527
630
|
ids: input.sectionIds,
|
|
528
631
|
names: input.sectionNames
|
|
529
632
|
});
|
|
530
|
-
|
|
633
|
+
// A band of pictures drawn with no picture at all is left out (AGL-4065).
|
|
634
|
+
if (!slots.length) return aiLayoutDropEmptyPictureBands(nodes, input);
|
|
531
635
|
let found = [];
|
|
532
636
|
const report = {
|
|
533
637
|
refused: new Map()
|
|
@@ -549,6 +653,17 @@ function rotate(list, by) {
|
|
|
549
653
|
alt: aiLayoutLabel('illustrativePhoto', input.language)
|
|
550
654
|
}) : photo;
|
|
551
655
|
/** The slots the source could not answer: no library, or it failed them. */ const unanswered = slots.map((_slot, index)=>!found[index]);
|
|
656
|
+
/**
|
|
657
|
+
* The gallery pictures the library answered with nothing fitting
|
|
658
|
+
* (AGL-4065): one of the job's own photos fills each, one the page does
|
|
659
|
+
* not show yet; so does a cell of a band of pictures, whatever its words
|
|
660
|
+
* say. Never a hero, a portrait of the business's people or a product,
|
|
661
|
+
* which keep their no-photo treatment (AGL-3831).
|
|
662
|
+
*/ const cells = aiLayoutPictureBandCells(nodes, slots);
|
|
663
|
+
const fitless = slots.map((slot, index)=>{
|
|
664
|
+
var _found_index;
|
|
665
|
+
return Boolean(found[index]) && !((_found_index = found[index]) == null ? void 0 : _found_index.src) && !slot.product && !slot.people && (slot.role === 'gallery' || slot.role === 'about' && Boolean(cells[index]));
|
|
666
|
+
});
|
|
552
667
|
const photos = slots.map((_slot, index)=>{
|
|
553
668
|
const answer = found[index];
|
|
554
669
|
return (answer == null ? void 0 : answer.src) ? answer : null;
|
|
@@ -578,6 +693,12 @@ function rotate(list, by) {
|
|
|
578
693
|
} : {}).forEach((pick, index)=>{
|
|
579
694
|
if (pick && unanswered[index]) photos[index] = pick;
|
|
580
695
|
});
|
|
696
|
+
// A gallery picture nothing fit takes a photo the page does not show yet, never one twice.
|
|
697
|
+
aiLayoutJobPhotoPicks(slots, fitless, own, photos, input.seed, {
|
|
698
|
+
repeats: 'never'
|
|
699
|
+
}).forEach((pick, index)=>{
|
|
700
|
+
if (pick && fitless[index]) photos[index] = pick;
|
|
701
|
+
});
|
|
581
702
|
}
|
|
582
703
|
// A failed slot takes a neutral starter only where the job had no photo
|
|
583
704
|
// of its own before this page, as #1831 had it. On a site start, where
|
|
@@ -591,8 +712,12 @@ function rotate(list, by) {
|
|
|
591
712
|
var _slots_index, _slots_index1;
|
|
592
713
|
return ((_slots_index = slots[index]) == null ? void 0 : _slots_index.people) || ((_slots_index1 = slots[index]) == null ? void 0 : _slots_index1.product) ? null : photo;
|
|
593
714
|
}) : hadOwn && !lateStart ? [] : aiLayoutNeutralStarterPhotos(slots, input.seed);
|
|
715
|
+
// On a site start, a gallery picture nothing fit that none of the job's
|
|
716
|
+
// photos fills yet keeps a neutral starter too, for the late photo pass
|
|
717
|
+
// (AGL-4065); anywhere else it is taken out.
|
|
718
|
+
const holds = (index)=>unanswered[index] || fitless[index] && lateStart && own.length > 0;
|
|
594
719
|
starters.forEach((photo, index)=>{
|
|
595
|
-
if (photo &&
|
|
720
|
+
if (photo && holds(index) && !photos[index]) photos[index] = said(photo);
|
|
596
721
|
});
|
|
597
722
|
// The page floor (AGL-3923; Zach, 2026-10-12: a starter photo before no
|
|
598
723
|
// photo at all): a page the library answered that would show no picture
|
|
@@ -640,7 +765,10 @@ function rotate(list, by) {
|
|
|
640
765
|
});
|
|
641
766
|
if (left.length) input.onStarters(left);
|
|
642
767
|
}
|
|
643
|
-
return aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds)
|
|
768
|
+
return aiLayoutDropEmptyPictureBands(aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds), _extends({}, input, {
|
|
769
|
+
slots,
|
|
770
|
+
photos
|
|
771
|
+
}));
|
|
644
772
|
} catch (error) {
|
|
645
773
|
console.error('ai layout pictures: the page keeps its empty picture slots', {
|
|
646
774
|
error
|