@aglyn/plugins-ai 1.0.0-beta.272 → 1.0.0-beta.273

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.
Files changed (52) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-site-start-card.component.js +237 -234
  3. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  4. package/src/lib/jobs/ai-empty-site-sweep.d.ts +12 -1
  5. package/src/lib/jobs/ai-empty-site-sweep.js +15 -3
  6. package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -1
  7. package/src/lib/jobs/ai-job-page-step.js +21 -2
  8. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  9. package/src/lib/jobs/ai-job-seo-step.js +8 -4
  10. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  11. package/src/lib/jobs/ai-job-site-datasets.d.ts +39 -1
  12. package/src/lib/jobs/ai-job-site-datasets.js +111 -15
  13. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  14. package/src/lib/jobs/ai-job-site-step.d.ts +12 -3
  15. package/src/lib/jobs/ai-job-site-step.js +85 -11
  16. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  17. package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
  18. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  19. package/src/lib/jobs/ai-jobs.d.ts +3 -0
  20. package/src/lib/jobs/ai-jobs.js +4 -0
  21. package/src/lib/jobs/ai-jobs.js.map +1 -1
  22. package/src/lib/jobs/ai-layout-owner-photos.js +4 -4
  23. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
  24. package/src/lib/jobs/ai-layout-stock-photos.d.ts +35 -0
  25. package/src/lib/jobs/ai-layout-stock-photos.js +114 -43
  26. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  27. package/src/lib/jobs/ai-site-late-photos.d.ts +143 -0
  28. package/src/lib/jobs/ai-site-late-photos.js +486 -0
  29. package/src/lib/jobs/ai-site-late-photos.js.map +1 -0
  30. package/src/lib/jobs/ai-site-publish.d.ts +18 -0
  31. package/src/lib/jobs/ai-site-publish.js +123 -5
  32. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  33. package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.d.ts +83 -0
  34. package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js +1553 -0
  35. package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js.map +1 -0
  36. package/src/lib/layout-language/ai-layout-pictures.d.ts +50 -13
  37. package/src/lib/layout-language/ai-layout-pictures.js +81 -19
  38. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  39. package/src/lib/model/ai-jobs.types.d.ts +10 -0
  40. package/src/lib/model/ai-jobs.types.js.map +1 -1
  41. package/src/lib/model/ai-site-photos.d.ts +119 -0
  42. package/src/lib/model/ai-site-photos.js +128 -0
  43. package/src/lib/model/ai-site-photos.js.map +1 -0
  44. package/src/lib/runtime/ai-doctrine-validators.d.ts +3 -1
  45. package/src/lib/runtime/ai-doctrine-validators.js +3 -1
  46. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  47. package/src/lib/runtime/seo-fields.d.ts +29 -4
  48. package/src/lib/runtime/seo-fields.js +58 -11
  49. package/src/lib/runtime/seo-fields.js.map +1 -1
  50. package/src/lib/tools/ai-seo-tool.d.ts +25 -1
  51. package/src/lib/tools/ai-seo-tool.js +61 -0
  52. package/src/lib/tools/ai-seo-tool.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-pictures.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 { DEFAULT_SITE_IMAGES } from '@aglyn/aglyn/app-utils/default-site'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from './ai-layout-labels'\n\n/**\n * THE PICTURES OF A LANGUAGE PAGE (AGL-3660).\n *\n * The compiler writes a picture slot as a frame holding an icon and an\n * `image` with alt text and no source (`ai-layout-compiler.ts`), and stays\n * pure: it never looks a photo up. This module is what runs after it, on the\n * stored page — it finds each empty slot, decides what kind of picture the\n * slot is, and fills it with a photo the site can serve from its own origin.\n *\n * Three sources fill a slot, in order:\n *\n * 1. A stock photo searched for by the slot's own description and copied into\n * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the\n * deployment has a stock-photo key.\n * 2. Where the library failed the slot — down, refusing, out of time, or its\n * photo not copied — one of the photos the same job already placed on\n * this site, on its other pages or its datasets' records\n * ({@link aiLayoutJobPhotoPicks}, AGL-3988): each was found for the\n * site's own world, so a wedding photographer's home page shows a wedding\n * and never an office.\n * 3. Where the job has no photo of its own — no stock-photo key, or the\n * library failed it from the start — one of the\n * STARTER PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`,\n * served at `/_static/starter/` by the tenant and the console alike,\n * credited in the CREDITS file beside them). They are the platform's own\n * files, so a visitor's browser asks no other host for them, and a\n * starter reads better than no picture when there is no library.\n *\n * Where the library answered and nothing in it fits a slot\n * ({@link AI_LAYOUT_NO_PHOTO}), the slot never takes a starter: the beta.257\n * Copper Kettle Catering start (2026-10-15) opened a barbecue caterer's home\n * page on the starters' pottery workbench (AGL-3831). Nor does it ship empty\n * (AGL-3856): its picture is taken out, frame and all\n * ({@link aiLayoutDropEmptyPictures}). A cover's frame is a black scrim laid\n * over the whole section, and with nothing in it the beta.261 Synchronicity\n * Gym hero read as a solid black block; a split's picture card is a rounded\n * gray box, and the beta.262 Adult Hybrid Learning Center hero read as\n * broken. Without them the section shows its own band, a cover's section\n * still pinned to the site's dark scheme so the words written for a dark\n * photo stay readable, and a split's words take the row's width.\n *\n * A slot keeps the alt text the model wrote only when it describes the photo\n * placed: a stock photo that is not what it says is described by what it is\n * (`aiStockPhotoAlt`), and a starter photo always by its own words, since\n * the model never wrote for it (AGL-3831). A stand-in the compiler drew where\n * the model described no picture is decorative while empty; the photo placed\n * there is described by its own words and is no longer decorative (AGL-3856).\n */\n\n/** What a picture slot is for, read from where it sits and what it says. */\nexport type AiLayoutPictureRole = 'hero' | 'about' | 'gallery'\n\n/** One empty picture slot of a stored page, in document order. */\nexport interface AiLayoutPictureSlot {\n /** The `image` node. */\n imageId: string\n /** The frame around it, which sets the slot's shape. */\n frameId: string | null\n /** The placeholder icon sharing the frame, taken out once a photo fills it. */\n iconId: string | null\n alt: string\n /** The frame's shape as width over height; 1 when it names none. */\n aspect: number\n /** The plan index of the section it sits in, or -1. */\n sectionIndex: number\n role: AiLayoutPictureRole\n /**\n * A product's photo (AGL-3676): what it is of, most specific first — its\n * name, then what its photo should show — searched for with the shop's\n * category and held to it, so a wick trimmer is never a laptop on a desk.\n */\n product?: { subjects: readonly string[] }\n /**\n * A stand-in the compiler drew where the model described no picture\n * (AGL-3856): its words are what a search looks for, never what a photo\n * shows, so a photo placed there is described by its own, and is no longer\n * decorative once it is.\n */\n standIn?: boolean\n /**\n * A portrait of one of the business's people (AGL-3856): a record of a\n * team, staff or instructors list. Its photo shows an adult, in the role\n * its words or its list name where one is listed, never a child or a thing.\n */\n people?: boolean\n /**\n * A dataset record's cover (AGL-3856): what the record is filed under — the\n * value of its list's category-like field (\"Weddings and elopements\") —\n * read after its own name for what its photo is OF. A photographer's record\n * is the photographed moment, never the photographer or the camera.\n */\n record?: { category?: string }\n}\n\n/** A photo a slot is filled with. */\nexport interface AiLayoutPicturePhoto {\n /** The site-served address: a starter path or the media library's own. */\n src: string\n width: number\n height: number\n /**\n * What the photo shows, where the slot's own alt text does not say it: the\n * slot takes it in place of the words the model wrote before the photo was\n * found. Absent, the slot's words describe it.\n */\n alt?: string\n}\n\n/** Words that say a picture shows people, or a section is about them. */\nconst PEOPLE =\n /\\b(team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\\b/i\n\n/**\n * \"About\" as what a section or a picture is (AGL-3923): leading its words\n * (\"About us\", \"About the studio\"), never the preposition inside them. The\n * beta.267 Brightvale closing band, \"Ready to talk about your marketing?\",\n * was read as a picture of people and searched \"ready talk marketing\" among\n * portraits, which answered a tin-can telephone.\n */\nconst ABOUT = /^\\W*about\\b/i\n\n/**\n * An `aspectRatio` (`'16 / 9'`) as a number, or `null` where none is named.\n * A responsive one (`{ xs: '4 / 3', md: '4 / 5' }`, as a designed page's\n * pictures carry, AGL-3660) reads at the desktop breakpoint first: one photo\n * serves every width, and the wide screen is where its shape shows most.\n */\nfunction aspectOf(value: unknown): number | null {\n if (value && typeof value === 'object') {\n const byBreakpoint = value as Record<string, unknown>\n for (const breakpoint of ['md', 'lg', 'xl', 'sm', 'xs']) {\n const ratio = aspectOf(byBreakpoint[breakpoint])\n if (ratio !== null) return ratio\n }\n return null\n }\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*\\/\\s*(\\d+(?:\\.\\d+)?)\\s*$/.exec(String(value ?? ''))\n if (!match) return null\n const ratio = Number(match[1]) / Number(match[2])\n return Number.isFinite(ratio) && ratio > 0 ? ratio : null\n}\n\n/** The shape a full-bleed cover's photo is searched for: it fills a wide band. */\nconst COVER_ASPECT = 16 / 9\n\n/**\n * A slot's shape (AGL-3660): the image's own `aspectRatio` (a designed\n * picture card sets it on the image), else its frame's, else — for a frame\n * laid absolutely over its whole section, a designed photo cover — a wide\n * band's. 1 where nothing says.\n */\nfunction slotAspect(image: { sx?: Record<string, unknown> } | undefined, frame: { sx?: Record<string, unknown> } | undefined): number {\n const own = aspectOf(image?.sx?.['aspectRatio']) ?? aspectOf(frame?.sx?.['aspectRatio'])\n if (own !== null) return own\n const sx = frame?.sx\n if (sx?.['position'] === 'absolute' && sx['width'] === '100%' && sx['height'] === '100%') return COVER_ASPECT\n return 1\n}\n\n/** A slot's role: the first section's picture is the hero; people make an about picture. */\nexport function aiLayoutPictureRole(input: {\n sectionIndex: number\n sectionName: string\n alt: string\n}): AiLayoutPictureRole {\n if (input.sectionIndex === 0) return 'hero'\n if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt) || ABOUT.test(input.sectionName) || ABOUT.test(input.alt)) return 'about'\n return 'gallery'\n}\n\n/**\n * Every empty picture slot of a stored page, walked from its root in\n * document order. A slot that already has a source — one the owner placed, or\n * an earlier pass filled — is not a slot.\n */\nexport function aiLayoutPictureSlots(\n nodes: NodesMap,\n rootId: string,\n sections: { ids: readonly string[]; names: readonly string[] },\n): AiLayoutPictureSlot[] {\n const map = nodes as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown>; sx?: Record<string, unknown>; nodes?: string[] }\n >\n const slots: AiLayoutPictureSlot[] = []\n const seen = new Set<string>()\n const visit = (id: string, parentId: string | null, sectionIndex: number) => {\n if (seen.has(id)) return\n seen.add(id)\n const node = map[id]\n if (!node) return\n const index = sections.ids.indexOf(id)\n const here = index >= 0 ? index : sectionIndex\n if (node.componentId === 'image') {\n const src = node.props?.['src']\n const alt = typeof node.props?.['alt'] === 'string' ? (node.props['alt'] as string).trim() : ''\n if ((typeof src !== 'string' || !src.trim()) && alt) {\n const frame = parentId ? map[parentId] : undefined\n const iconId =\n frame?.nodes?.find((child) => child !== id && map[child]?.componentId === 'icon') ?? null\n slots.push({\n imageId: id,\n frameId: frame ? parentId : null,\n iconId,\n alt,\n aspect: slotAspect(node, frame),\n sectionIndex: here,\n ...(node.props?.['decorative'] === true ? { standIn: true } : {}),\n role: aiLayoutPictureRole({\n sectionIndex: here,\n sectionName: here >= 0 ? (sections.names[here] ?? '') : '',\n alt,\n }),\n })\n }\n }\n for (const child of node.nodes ?? []) visit(child, id, here)\n }\n visit(rootId, null, -1)\n return slots\n}\n\n/** The starter photos, by name, each with the words that describe it. */\nexport const AI_LAYOUT_STARTER_PHOTOS = DEFAULT_SITE_IMAGES\n\n/**\n * A source's answer for a slot the library answered and nothing in it fits\n * (AGL-3831): the slot keeps its no-photo treatment rather than take a\n * starter. Its source is empty, so a caller reading `src` to fill something\n * that must hold a photo (a product's, a record's) falls back as it would on\n * `null`.\n */\nexport const AI_LAYOUT_NO_PHOTO: Readonly<AiLayoutPicturePhoto> = Object.freeze({ src: '', width: 0, height: 0 })\n\ntype StarterName = keyof typeof AI_LAYOUT_STARTER_PHOTOS\n\n/**\n * The starter photos each role draws from, best first. A hero draws from the\n * wide photos that read as a page's opening, an about picture leads with the\n * owner, and the rest are the gallery's.\n */\nconst STARTER_POOLS: Readonly<Record<AiLayoutPictureRole, readonly StarterName[]>> = {\n hero: ['hero', 'desk', 'craft'],\n about: ['about', 'hero'],\n gallery: ['desk', 'plants', 'craft'],\n}\n\nconst ALL_STARTERS = Object.keys(AI_LAYOUT_STARTER_PHOTOS) as StarterName[]\n\n/** A small stable number from a seed, so the same job always picks the same photos. */\nexport function aiLayoutSeedNumber(seed: string): number {\n let hash = 2166136261\n for (let index = 0; index < seed.length; index += 1) {\n hash ^= seed.charCodeAt(index)\n hash = Math.imul(hash, 16777619)\n }\n return hash >>> 0\n}\n\nfunction rotate<T>(list: readonly T[], by: number): T[] {\n if (!list.length) return []\n const start = by % list.length\n return [...list.slice(start), ...list.slice(0, start)]\n}\n\n/**\n * A starter photo for each slot (AGL-3660). No photo repeats on a page while\n * an unused one remains, and every pool turns by the seed, so sites do not\n * all open with the same photo while one job always picks the same.\n *\n * The slots are served by what they are for, not where they sit: the hero\n * first, then the pictures of people (an about picture leads with the owner),\n * then the rest. The live Juniper Clay start (2026-10-08) had six slots and\n * five starters, and the owner's photo went to a stoneware card before the\n * About section that described the artist asked for it. Only a page with more\n * slots than starters repeats one: the photo placed longest ago, never the\n * one in a slot beside it. The owner's portrait goes only to a picture of\n * people; a gallery picture never takes it, even to avoid a repeat.\n */\nexport function aiLayoutStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): AiLayoutPicturePhoto[] {\n const turn = aiLayoutSeedNumber(seed)\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const names: Array<StarterName | undefined> = slots.map(() => undefined)\n /** When each starter was last placed, by its turn in the serving order. */\n const placedAt = new Map<StarterName, number>()\n served.forEach(({ slot, index }, step) => {\n // An about picture leads with the owner on every site; the others turn.\n const pool = slot.role === 'about' ? [...STARTER_POOLS.about] : rotate(STARTER_POOLS[slot.role], turn)\n const rest = rotate(\n ALL_STARTERS.filter((name) => !pool.includes(name)),\n turn,\n )\n // The owner's portrait is a picture of a person, and only a picture of\n // people takes it: never a vase in a gallery (AGL-3660). A portrait of\n // one of the business's people takes only people, repeated before a\n // workbench stands in for a teacher (AGL-3856).\n const candidates = slot.people\n ? [...STARTER_POOLS.about]\n : [...pool, ...rest].filter((name) => slot.role === 'about' || name !== 'about')\n const unused = candidates.find((candidate) => !placedAt.has(candidate))\n const beside = new Set([names[index - 1], names[index + 1]].filter(Boolean))\n const name =\n unused ??\n [...candidates]\n .sort((a, b) => (placedAt.get(a) ?? -1) - (placedAt.get(b) ?? -1))\n .find((candidate) => !beside.has(candidate)) ??\n candidates[0]\n names[index] = name\n placedAt.set(name, step)\n })\n return names.map((name) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[name as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * The starters that show no one's business (AGL-3923): a team at a table in a\n * bright office, a tidy desk. Never the workbench, the coffee shop's owner or\n * the houseplants, which read as another business's own photos (AGL-3831).\n */\nconst NEUTRAL_STARTERS: readonly StarterName[] = ['hero', 'desk']\n\n/**\n * A neutral starter for each of a list's records (AGL-3988), where the site\n * has a library and the job has no photo of its own: the two taken in turn\n * from the seed, so no record shows the photo of the one beside it. A card\n * of a listing cannot drop its frame as a page's picture does, so every\n * record takes one. Pure.\n */\nexport function aiLayoutNeutralStarterRun(count: number, seed: string): AiLayoutPicturePhoto[] {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n return Array.from({ length: Math.max(0, count) }, (_, index) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[pool[index % pool.length] as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * A neutral starter for each slot a library could not fill, where the site\n * has a library (AGL-3923): each at most once on a page, the hero served\n * first and each pool turned by the seed, and none for a portrait of the\n * business's people or a product, nor for a slot past the two. A slot given\n * none keeps the no-photo treatment.\n */\nexport function aiLayoutNeutralStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): Array<AiLayoutPicturePhoto | null> {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n const order: AiLayoutPictureRole[] = ['hero', 'gallery', 'about']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot }) => !slot.people && !slot.product)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n served.forEach(({ index }, step) => {\n const name = pool[step]\n if (!name) return\n const photo = AI_LAYOUT_STARTER_PHOTOS[name]\n photos[index] = { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n return photos\n}\n\n/** A text's words a photo's own words may share: lowercase, four letters or more. */\nconst wordsOf = (text: string): Set<string> =>\n new Set(\n text\n .toLowerCase()\n .split(/[^a-zÀ-ɏ]+/)\n .filter((word) => word.length >= 4),\n )\n\n/**\n * The job's own photos for the slots a library could not fill (AGL-3988):\n * the photos THIS job already placed on this site — on its other pages, on\n * its datasets' records, and on this page — which were each found for the\n * site's own world, before any starter. The beta.270 Sugar Hollow\n * Photography start (2026-10-19) opened a wedding photographer's home page\n * on the starters' desk and an office team, and filed five wedding galleries\n * under a workbench, a windowsill and the same office, while the library\n * had already given the job three wedding and family photos.\n *\n * Each slot `need` names takes the one that fits it best: one this page does\n * not show yet before one it does, a wide photo for a hero, the shape\n * nearest its frame, and the most words shared with what the slot says it\n * shows; never the photo of the slot beside it. A product's photo and a\n * portrait of the business's people are of one named thing, which no other\n * photo is, so they take none. Among equals the seed picks, so one job\n * always picks the same. `null` for a slot given none. Pure.\n */\nexport function aiLayoutJobPhotoPicks(\n slots: readonly AiLayoutPictureSlot[],\n need: readonly boolean[],\n jobPhotos: readonly AiLayoutPicturePhoto[],\n placed: ReadonlyArray<AiLayoutPicturePhoto | null | undefined>,\n seed: string,\n): Array<AiLayoutPicturePhoto | null> {\n const seen = new Set<string>()\n const pool = jobPhotos.filter((photo) => {\n if (!photo.src || !(photo.width > 0) || !(photo.height > 0) || seen.has(photo.src)) return false\n seen.add(photo.src)\n return true\n })\n const picks: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n if (!pool.length) return picks\n const shown = slots.map((_slot, index) => placed[index]?.src || '')\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot, index }) => need[index] && !slot.product && !slot.people)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const turn = aiLayoutSeedNumber(seed)\n for (const { slot, index } of served) {\n const beside = new Set([shown[index - 1], shown[index + 1]].filter(Boolean))\n const says = wordsOf(slot.alt)\n let best: { photo: AiLayoutPicturePhoto; score: number; rank: number } | null = null\n for (const [at, photo] of pool.entries()) {\n if (beside.has(photo.src)) continue\n const aspect = photo.width / photo.height\n const uses = shown.filter((src) => src === photo.src).length\n const shared = [...wordsOf(photo.alt ?? '')].filter((word) => says.has(word)).length\n const score =\n -100 * uses +\n (slot.role === 'hero' && aspect >= 1.3 ? 20 : 0) -\n 10 * Math.abs(Math.log(aspect / (slot.aspect > 0 ? slot.aspect : 1))) +\n 4 * shared\n // The seed turns the pool, so equals are taken in a per-job order.\n const rank = (at - (turn % pool.length) + pool.length) % pool.length\n if (!best || score > best.score || (score === best.score && rank < best.rank)) best = { photo, score, rank }\n }\n if (!best) continue\n picks[index] = { ...best.photo }\n shown[index] = best.photo.src\n }\n return picks\n}\n\n/**\n * The page with each slot filled by its photo: the `image` given its source,\n * its intrinsic size and how it loads (the hero eagerly, as the starter home\n * page loads its own), and the placeholder icon taken out of the frame. The\n * alt text is kept as the model wrote it unless the photo brings its own\n * words. A slot given no photo is left as the compiler wrote it.\n */\nexport function aiLayoutPlacePictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n): NodesMap {\n const next = { ...(nodes as unknown as Record<string, Record<string, unknown>>) }\n // Only the page's first picture loads eagerly (rule 16, AGL-3660): a hero\n // of several photos loaded them all eagerly, and a live Portfolio page was\n // refused for it (2026-10-10). The slots are in document order; every later\n // picture leaves `loading` unset, which the renderer loads when reached.\n const lead = slots.findIndex((_, index) => Boolean(photos[index] && next[slots[index].imageId]))\n slots.forEach((slot, index) => {\n const photo = photos[index]\n const node = next[slot.imageId]\n if (!photo || !node) return\n const props = { ...((node['props'] as Record<string, unknown> | undefined) ?? {}) }\n delete props['loading']\n // A photo placed is described by its words, never hidden from a screen\n // reader: a stand-in was decorative only while it showed nothing (AGL-3856).\n // One that brings no words of its own, such as an owner's upload with no\n // alt text, keeps the slot's (AGL-3979): a picture a visitor sees always\n // has a description.\n const alt = photo.alt?.trim() || (typeof props['alt'] === 'string' ? props['alt'].trim() : '')\n if (alt) delete props['decorative']\n next[slot.imageId] = {\n ...node,\n props: {\n ...props,\n src: photo.src,\n intrinsicWidth: photo.width,\n intrinsicHeight: photo.height,\n ...(alt ? { alt } : {}),\n ...(index === lead && slot.role === 'hero' ? { loading: 'eager' } : {}),\n },\n }\n if (slot.iconId && slot.frameId && next[slot.frameId]) {\n const frame = next[slot.frameId]\n next[slot.frameId] = {\n ...frame,\n nodes: ((frame['nodes'] as string[] | undefined) ?? []).filter((child) => child !== slot.iconId),\n }\n delete next[slot.iconId]\n }\n })\n return next as unknown as NodesMap\n}\n\n/** Whether a node is a grid row: a `muiGrid` container. */\nconst isGridRow = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && (node['props'] as Record<string, unknown> | undefined)?.['container'] === true\n\n/** Whether a node is a column of a grid row: a sized `muiGrid` item. */\nconst isGridColumn = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && !isGridRow(node)\n\n/**\n * The page without the pictures left with no photo (AGL-3856). A slot whose\n * photo is missing loses its image, its icon and its frame where the frame\n * holds nothing else, and so does every wrapper that holds nothing once it\n * is gone, up to its section. A cover's black scrim over nothing read as a\n * solid black block (the beta.261 Synchronicity Gym hero), and an empty\n * rounded gray card as a broken picture (the beta.262 Adult Hybrid Learning\n * Center hero). A column of a split row taken out leaves the words' column\n * the row's full width. A cover's section stays pinned to the site's dark\n * scheme, which paints that scheme's background and text colors, so the\n * words written for a dark photo stay readable. A slot a photo fills, a\n * starter's included, is kept.\n */\nexport function aiLayoutDropEmptyPictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n sectionIds: readonly string[] = [],\n): NodesMap {\n const map = nodes as unknown as Record<string, Record<string, unknown>>\n const empty = slots.filter((slot, index) => {\n if (photos[index]?.src) return false\n const src = (map[slot.imageId]?.['props'] as Record<string, unknown> | undefined)?.['src']\n return Boolean(map[slot.imageId]) && !(typeof src === 'string' && src.trim())\n })\n if (!empty.length) return nodes\n const next = { ...map }\n const children = (id: string) => (next[id]?.['nodes'] as string[] | undefined) ?? []\n const parentOf = (id: string) => Object.keys(next).find((key) => children(key).includes(id)) ?? null\n const remove = (id: string) => {\n const parent = parentOf(id)\n if (parent) next[parent] = { ...next[parent], nodes: children(parent).filter((child) => child !== id) }\n delete next[id]\n return parent\n }\n const sections = new Set(sectionIds)\n for (const slot of empty) {\n const own = new Set([slot.imageId, slot.iconId].filter(Boolean) as string[])\n const frameHoldsOnly = Boolean(slot.frameId && next[slot.frameId] && children(slot.frameId).every((child) => own.has(child)))\n let parent = remove(slot.imageId)\n if (slot.iconId && next[slot.iconId]) remove(slot.iconId)\n if (!frameHoldsOnly) continue\n // The frame, then each wrapper it leaves empty: never a section, nor the page's root.\n let at: string | null = slot.frameId\n while (at && next[at] && !children(at).length && !sections.has(at) && parentOf(at) !== null) {\n const column = isGridColumn(next[at])\n parent = remove(at)\n at = column ? null : parent\n }\n // A split row left with one column gives its words the row's width.\n if (parent && isGridRow(next[parent])) {\n const [only, ...rest] = children(parent)\n if (only && !rest.length && isGridColumn(next[only])) {\n const column = next[only] as Record<string, unknown>\n const sx = { ...((column['sx'] as Record<string, unknown> | undefined) ?? {}) }\n delete sx['order']\n next[only] = {\n ...column,\n props: { ...((column['props'] as Record<string, unknown> | undefined) ?? {}), size: 'xs:12' },\n sx: Object.keys(sx).length ? sx : null,\n }\n }\n }\n }\n return next as unknown as NodesMap\n}\n\n/**\n * A source of photos found for the slots themselves — a stock search copied\n * into the site's library — answering per slot, in order: a photo; or\n * {@link AI_LAYOUT_NO_PHOTO} where the library answered and nothing fits,\n * which leaves the slot without a photo; or `null` where the library could\n * not be asked, which takes a starter photo. It may throw, and then every\n * slot takes a starter.\n */\nexport type AiLayoutPictureSource = (\n slots: readonly AiLayoutPictureSlot[],\n) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>\n\n/**\n * Fills a stored page's empty picture slots (AGL-3660): from `source` where\n * one is given and it answers; a slot it answers with nothing fitting stays\n * without a photo and is taken out ({@link aiLayoutDropEmptyPictures}). A\n * slot it could not answer — the library down, out of time, or its photo not\n * copied — takes one of the photos the job already placed on this site\n * (`jobPhotos`, {@link aiLayoutJobPhotoPicks}, AGL-3988), and only where the\n * job has none of its own, a starter photo described by its own words\n * (AGL-3831). Never throws, and never leaves a page worse than it came: a\n * picture is decoration, and a job never fails over one.\n */\nexport async function aiResolveLayoutPictures(\n nodes: NodesMap,\n input: {\n rootId: string\n sectionIds: readonly string[]\n sectionNames: readonly string[]\n /** A per-job word, so two sites do not open with the same photo. */\n seed: string\n source?: AiLayoutPictureSource | null\n /**\n * The photos this job already placed on this site (AGL-3988): its other\n * pages' and its datasets' records', each found for the site's world.\n * Taken before any starter where `source` could not fill a slot.\n */\n jobPhotos?: readonly AiLayoutPicturePhoto[]\n /** The site's language (AGL-3750); a starter photo's English words are not its own. */\n language?: string\n },\n): Promise<NodesMap> {\n try {\n const slots = aiLayoutPictureSlots(nodes, input.rootId, {\n ids: input.sectionIds,\n names: input.sectionNames,\n })\n if (!slots.length) return nodes\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n if (input.source) {\n try {\n found = await input.source(slots)\n } catch (error) {\n console.warn('ai layout pictures: the stock source failed; the job’s own photos or starters fill the page', { error })\n found = []\n }\n }\n // The starters' words are English; a site in another language says it\n // shows an illustrative photo, true of any photo, as a stock photo's does.\n const otherLanguage = aiLayoutSpeaksOtherThanEnglish(input.language)\n const said = (photo: AiLayoutPicturePhoto): AiLayoutPicturePhoto =>\n otherLanguage ? { ...photo, alt: aiLayoutLabel('illustrativePhoto', input.language) } : photo\n /** The slots the source could not answer: no library, or it failed them. */\n const unanswered = slots.map((_slot, index) => !found[index])\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map((_slot, index) => {\n const answer = found[index]\n return answer?.src ? answer : null\n })\n // With a library, the job's own photos come before any starter (AGL-3988),\n // and a starter is only ever a neutral one (AGL-3923): the beta.267\n // Brightvale start, whose library refused its copies, showed a pottery\n // bench and a coffee shop's owner as a marketer's pictures.\n const own = input.source ? (input.jobPhotos ?? []).filter((photo) => photo.src && photo.width > 0 && photo.height > 0) : []\n if (own.length) {\n aiLayoutJobPhotoPicks(slots, unanswered, own, photos, input.seed).forEach((pick, index) => {\n if (pick && unanswered[index]) photos[index] = pick\n })\n } else {\n const starters = input.source ? aiLayoutNeutralStarterPhotos(slots, input.seed) : aiLayoutStarterPhotos(slots, input.seed)\n starters.forEach((photo, index) => {\n if (photo && unanswered[index]) photos[index] = said(photo)\n })\n }\n // The page floor (AGL-3923; Zach, 2026-10-12: a starter photo before no\n // photo at all): a page the library answered that would show no picture\n // keeps one, in its hero, else its first slot: one of the job's own\n // photos where it has any (AGL-3988), else a neutral starter.\n if (input.source && !photos.some((photo) => photo?.src)) {\n const at = Math.max(0, slots.findIndex((slot) => slot.role === 'hero'))\n const lead = { ...(slots[at] as AiLayoutPictureSlot), product: undefined, people: undefined }\n const [pick] = aiLayoutJobPhotoPicks([lead], [true], own, [null], input.seed)\n const [floor] = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(input.seed))\n const starter = floor ? AI_LAYOUT_STARTER_PHOTOS[floor] : null\n if (pick) photos[at] = pick\n else if (starter) photos[at] = said({ src: starter.src, width: starter.width, height: starter.height, alt: starter.alt })\n }\n return aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds)\n } catch (error) {\n console.error('ai layout pictures: the page keeps its empty picture slots', { error })\n return nodes\n }\n}\n"],"names":["DEFAULT_SITE_IMAGES","aiLayoutLabel","aiLayoutSpeaksOtherThanEnglish","PEOPLE","ABOUT","aspectOf","value","byBreakpoint","breakpoint","ratio","match","exec","String","Number","isFinite","COVER_ASPECT","slotAspect","image","frame","own","sx","aiLayoutPictureRole","input","sectionIndex","test","sectionName","alt","aiLayoutPictureSlots","nodes","rootId","sections","map","slots","seen","Set","visit","id","parentId","node","has","add","index","ids","indexOf","here","componentId","src","props","trim","undefined","iconId","find","child","push","imageId","frameId","aspect","standIn","role","names","AI_LAYOUT_STARTER_PHOTOS","AI_LAYOUT_NO_PHOTO","Object","freeze","width","height","STARTER_POOLS","hero","about","gallery","ALL_STARTERS","keys","aiLayoutSeedNumber","seed","hash","length","charCodeAt","Math","imul","rotate","list","by","start","slice","aiLayoutStarterPhotos","turn","order","served","slot","sort","a","b","placedAt","Map","forEach","step","unused","pool","rest","filter","name","includes","candidates","people","candidate","beside","Boolean","get","set","photo","NEUTRAL_STARTERS","aiLayoutNeutralStarterRun","count","Array","from","max","_","aiLayoutNeutralStarterPhotos","product","photos","wordsOf","text","toLowerCase","split","word","aiLayoutJobPhotoPicks","need","jobPhotos","placed","picks","shown","_slot","says","best","at","entries","uses","shared","score","abs","log","rank","aiLayoutPlacePictures","next","lead","findIndex","intrinsicWidth","intrinsicHeight","loading","isGridRow","isGridColumn","aiLayoutDropEmptyPictures","sectionIds","empty","children","parentOf","key","remove","parent","frameHoldsOnly","every","column","only","size","aiResolveLayoutPictures","sectionNames","found","source","error","console","warn","otherLanguage","language","said","unanswered","answer","pick","starters","some","floor","starter"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-pictures.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 { DEFAULT_SITE_IMAGES } from '@aglyn/aglyn/app-utils/default-site'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiSiteIsStarterSrc, type AiSiteStarterSlot, type AiStockRefusedPicks } from '../model/ai-site-photos'\nimport { aiLayoutLabel, aiLayoutSpeaksOtherThanEnglish } from './ai-layout-labels'\n\n/**\n * THE PICTURES OF A LANGUAGE PAGE (AGL-3660).\n *\n * The compiler writes a picture slot as a frame holding an icon and an\n * `image` with alt text and no source (`ai-layout-compiler.ts`), and stays\n * pure: it never looks a photo up. This module is what runs after it, on the\n * stored page — it finds each empty slot, decides what kind of picture the\n * slot is, and fills it with a photo the site can serve from its own origin.\n *\n * Three sources fill a slot, in order:\n *\n * 1. A stock photo searched for by the slot's own description and copied into\n * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the\n * deployment has a stock-photo key.\n * 2. Where the library failed the slot — down, refusing, out of time, or its\n * photo not copied — one of the photos the same job already placed on\n * this site, on its other pages, its datasets' records or this page's\n * other slots ({@link aiLayoutJobPhotoPicks}, AGL-3988): each was found\n * for the site's own world, so a wedding photographer's home page shows a\n * wedding and never an office.\n * 3. Where the job has no photo of its own — no stock-photo key, or the\n * library failed it before the job copied any — one of the\n * STARTER PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`,\n * served at `/_static/starter/` by the tenant and the console alike,\n * credited in the CREDITS file beside them). They are the platform's own\n * files, so a visitor's browser asks no other host for them, and a\n * starter reads better than no picture when there is no library. On a\n * site start, which never places a photo its page already shows, a slot\n * none of the job's other photos fits yet keeps one too, and the start's\n * late photo pass gives it one of the job's own photos once every page\n * is built (`jobs/ai-site-late-photos.ts`).\n *\n * Where the library answered and nothing in it fits a slot\n * ({@link AI_LAYOUT_NO_PHOTO}), the slot never takes a starter: the beta.257\n * Copper Kettle Catering start (2026-10-15) opened a barbecue caterer's home\n * page on the starters' pottery workbench (AGL-3831). Nor does it ship empty\n * (AGL-3856): its picture is taken out, frame and all\n * ({@link aiLayoutDropEmptyPictures}). A cover's frame is a black scrim laid\n * over the whole section, and with nothing in it the beta.261 Synchronicity\n * Gym hero read as a solid black block; a split's picture card is a rounded\n * gray box, and the beta.262 Adult Hybrid Learning Center hero read as\n * broken. Without them the section shows its own band, a cover's section\n * still pinned to the site's dark scheme so the words written for a dark\n * photo stay readable, and a split's words take the row's width.\n *\n * A slot keeps the alt text the model wrote only when it describes the photo\n * placed: a stock photo that is not what it says is described by what it is\n * (`aiStockPhotoAlt`), and a starter photo always by its own words, since\n * the model never wrote for it (AGL-3831). A stand-in the compiler drew where\n * the model described no picture is decorative while empty; the photo placed\n * there is described by its own words and is no longer decorative (AGL-3856).\n */\n\n/** What a picture slot is for, read from where it sits and what it says. */\nexport type AiLayoutPictureRole = 'hero' | 'about' | 'gallery'\n\n/** One empty picture slot of a stored page, in document order. */\nexport interface AiLayoutPictureSlot {\n /** The `image` node. */\n imageId: string\n /** The frame around it, which sets the slot's shape. */\n frameId: string | null\n /** The placeholder icon sharing the frame, taken out once a photo fills it. */\n iconId: string | null\n alt: string\n /** The frame's shape as width over height; 1 when it names none. */\n aspect: number\n /** The plan index of the section it sits in, or -1. */\n sectionIndex: number\n role: AiLayoutPictureRole\n /**\n * A product's photo (AGL-3676): what it is of, most specific first — its\n * name, then what its photo should show — searched for with the shop's\n * category and held to it, so a wick trimmer is never a laptop on a desk.\n */\n product?: { subjects: readonly string[] }\n /**\n * A stand-in the compiler drew where the model described no picture\n * (AGL-3856): its words are what a search looks for, never what a photo\n * shows, so a photo placed there is described by its own, and is no longer\n * decorative once it is.\n */\n standIn?: boolean\n /**\n * A portrait of one of the business's people (AGL-3856): a record of a\n * team, staff or instructors list. Its photo shows an adult, in the role\n * its words or its list name where one is listed, never a child or a thing.\n */\n people?: boolean\n /**\n * A dataset record's cover (AGL-3856): what the record is filed under — the\n * value of its list's category-like field (\"Weddings and elopements\") —\n * read after its own name for what its photo is OF. A photographer's record\n * is the photographed moment, never the photographer or the camera.\n */\n record?: { category?: string }\n}\n\n/** A photo a slot is filled with. */\nexport interface AiLayoutPicturePhoto {\n /** The site-served address: a starter path or the media library's own. */\n src: string\n width: number\n height: number\n /**\n * What the photo shows, where the slot's own alt text does not say it: the\n * slot takes it in place of the words the model wrote before the photo was\n * found. Absent, the slot's words describe it.\n */\n alt?: string\n}\n\n/** Words that say a picture shows people, or a section is about them. */\nconst PEOPLE =\n /\\b(team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\\b/i\n\n/**\n * \"About\" as what a section or a picture is (AGL-3923): leading its words\n * (\"About us\", \"About the studio\"), never the preposition inside them. The\n * beta.267 Brightvale closing band, \"Ready to talk about your marketing?\",\n * was read as a picture of people and searched \"ready talk marketing\" among\n * portraits, which answered a tin-can telephone.\n */\nconst ABOUT = /^\\W*about\\b/i\n\n/**\n * An `aspectRatio` (`'16 / 9'`) as a number, or `null` where none is named.\n * A responsive one (`{ xs: '4 / 3', md: '4 / 5' }`, as a designed page's\n * pictures carry, AGL-3660) reads at the desktop breakpoint first: one photo\n * serves every width, and the wide screen is where its shape shows most.\n */\nfunction aspectOf(value: unknown): number | null {\n if (value && typeof value === 'object') {\n const byBreakpoint = value as Record<string, unknown>\n for (const breakpoint of ['md', 'lg', 'xl', 'sm', 'xs']) {\n const ratio = aspectOf(byBreakpoint[breakpoint])\n if (ratio !== null) return ratio\n }\n return null\n }\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*\\/\\s*(\\d+(?:\\.\\d+)?)\\s*$/.exec(String(value ?? ''))\n if (!match) return null\n const ratio = Number(match[1]) / Number(match[2])\n return Number.isFinite(ratio) && ratio > 0 ? ratio : null\n}\n\n/** The shape a full-bleed cover's photo is searched for: it fills a wide band. */\nconst COVER_ASPECT = 16 / 9\n\n/**\n * A slot's shape (AGL-3660): the image's own `aspectRatio` (a designed\n * picture card sets it on the image), else its frame's, else — for a frame\n * laid absolutely over its whole section, a designed photo cover — a wide\n * band's. 1 where nothing says.\n */\nfunction slotAspect(image: { sx?: Record<string, unknown> } | undefined, frame: { sx?: Record<string, unknown> } | undefined): number {\n const own = aspectOf(image?.sx?.['aspectRatio']) ?? aspectOf(frame?.sx?.['aspectRatio'])\n if (own !== null) return own\n const sx = frame?.sx\n if (sx?.['position'] === 'absolute' && sx['width'] === '100%' && sx['height'] === '100%') return COVER_ASPECT\n return 1\n}\n\n/** A slot's role: the first section's picture is the hero; people make an about picture. */\nexport function aiLayoutPictureRole(input: {\n sectionIndex: number\n sectionName: string\n alt: string\n}): AiLayoutPictureRole {\n if (input.sectionIndex === 0) return 'hero'\n if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt) || ABOUT.test(input.sectionName) || ABOUT.test(input.alt)) return 'about'\n return 'gallery'\n}\n\n/**\n * Every empty picture slot of a stored page, walked from its root in\n * document order. A slot that already has a source — one the owner placed, or\n * an earlier pass filled — is not a slot.\n */\nexport function aiLayoutPictureSlots(\n nodes: NodesMap,\n rootId: string,\n sections: { ids: readonly string[]; names: readonly string[] },\n): AiLayoutPictureSlot[] {\n const map = nodes as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown>; sx?: Record<string, unknown>; nodes?: string[] }\n >\n const slots: AiLayoutPictureSlot[] = []\n const seen = new Set<string>()\n const visit = (id: string, parentId: string | null, sectionIndex: number) => {\n if (seen.has(id)) return\n seen.add(id)\n const node = map[id]\n if (!node) return\n const index = sections.ids.indexOf(id)\n const here = index >= 0 ? index : sectionIndex\n if (node.componentId === 'image') {\n const src = node.props?.['src']\n const alt = typeof node.props?.['alt'] === 'string' ? (node.props['alt'] as string).trim() : ''\n if ((typeof src !== 'string' || !src.trim()) && alt) {\n const frame = parentId ? map[parentId] : undefined\n const iconId =\n frame?.nodes?.find((child) => child !== id && map[child]?.componentId === 'icon') ?? null\n slots.push({\n imageId: id,\n frameId: frame ? parentId : null,\n iconId,\n alt,\n aspect: slotAspect(node, frame),\n sectionIndex: here,\n ...(node.props?.['decorative'] === true ? { standIn: true } : {}),\n role: aiLayoutPictureRole({\n sectionIndex: here,\n sectionName: here >= 0 ? (sections.names[here] ?? '') : '',\n alt,\n }),\n })\n }\n }\n for (const child of node.nodes ?? []) visit(child, id, here)\n }\n visit(rootId, null, -1)\n return slots\n}\n\n/** The starter photos, by name, each with the words that describe it. */\nexport const AI_LAYOUT_STARTER_PHOTOS = DEFAULT_SITE_IMAGES\n\n/**\n * A source's answer for a slot the library answered and nothing in it fits\n * (AGL-3831): the slot keeps its no-photo treatment rather than take a\n * starter. Its source is empty, so a caller reading `src` to fill something\n * that must hold a photo (a product's, a record's) falls back as it would on\n * `null`.\n */\nexport const AI_LAYOUT_NO_PHOTO: Readonly<AiLayoutPicturePhoto> = Object.freeze({ src: '', width: 0, height: 0 })\n\ntype StarterName = keyof typeof AI_LAYOUT_STARTER_PHOTOS\n\n/**\n * The starter photos each role draws from, best first. A hero draws from the\n * wide photos that read as a page's opening, an about picture leads with the\n * owner, and the rest are the gallery's.\n */\nconst STARTER_POOLS: Readonly<Record<AiLayoutPictureRole, readonly StarterName[]>> = {\n hero: ['hero', 'desk', 'craft'],\n about: ['about', 'hero'],\n gallery: ['desk', 'plants', 'craft'],\n}\n\nconst ALL_STARTERS = Object.keys(AI_LAYOUT_STARTER_PHOTOS) as StarterName[]\n\n/** A small stable number from a seed, so the same job always picks the same photos. */\nexport function aiLayoutSeedNumber(seed: string): number {\n let hash = 2166136261\n for (let index = 0; index < seed.length; index += 1) {\n hash ^= seed.charCodeAt(index)\n hash = Math.imul(hash, 16777619)\n }\n return hash >>> 0\n}\n\nfunction rotate<T>(list: readonly T[], by: number): T[] {\n if (!list.length) return []\n const start = by % list.length\n return [...list.slice(start), ...list.slice(0, start)]\n}\n\n/**\n * A starter photo for each slot (AGL-3660). No photo repeats on a page while\n * an unused one remains, and every pool turns by the seed, so sites do not\n * all open with the same photo while one job always picks the same.\n *\n * The slots are served by what they are for, not where they sit: the hero\n * first, then the pictures of people (an about picture leads with the owner),\n * then the rest. The live Juniper Clay start (2026-10-08) had six slots and\n * five starters, and the owner's photo went to a stoneware card before the\n * About section that described the artist asked for it. Only a page with more\n * slots than starters repeats one: the photo placed longest ago, never the\n * one in a slot beside it. The owner's portrait goes only to a picture of\n * people; a gallery picture never takes it, even to avoid a repeat.\n */\nexport function aiLayoutStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): AiLayoutPicturePhoto[] {\n const turn = aiLayoutSeedNumber(seed)\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const names: Array<StarterName | undefined> = slots.map(() => undefined)\n /** When each starter was last placed, by its turn in the serving order. */\n const placedAt = new Map<StarterName, number>()\n served.forEach(({ slot, index }, step) => {\n // An about picture leads with the owner on every site; the others turn.\n const pool = slot.role === 'about' ? [...STARTER_POOLS.about] : rotate(STARTER_POOLS[slot.role], turn)\n const rest = rotate(\n ALL_STARTERS.filter((name) => !pool.includes(name)),\n turn,\n )\n // The owner's portrait is a picture of a person, and only a picture of\n // people takes it: never a vase in a gallery (AGL-3660). A portrait of\n // one of the business's people takes only people, repeated before a\n // workbench stands in for a teacher (AGL-3856).\n const candidates = slot.people\n ? [...STARTER_POOLS.about]\n : [...pool, ...rest].filter((name) => slot.role === 'about' || name !== 'about')\n const unused = candidates.find((candidate) => !placedAt.has(candidate))\n const beside = new Set([names[index - 1], names[index + 1]].filter(Boolean))\n const name =\n unused ??\n [...candidates]\n .sort((a, b) => (placedAt.get(a) ?? -1) - (placedAt.get(b) ?? -1))\n .find((candidate) => !beside.has(candidate)) ??\n candidates[0]\n names[index] = name\n placedAt.set(name, step)\n })\n return names.map((name) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[name as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * The starters that show no one's business (AGL-3923): a team at a table in a\n * bright office, a tidy desk. Never the workbench, the coffee shop's owner or\n * the houseplants, which read as another business's own photos (AGL-3831).\n */\nconst NEUTRAL_STARTERS: readonly StarterName[] = ['hero', 'desk']\n\n/**\n * A neutral starter for each of a list's records (AGL-3988), where the site\n * has a library and the job has no photo of its own: the two taken in turn\n * from the seed, so no record shows the photo of the one beside it. A card\n * of a listing cannot drop its frame as a page's picture does, so every\n * record takes one. Pure.\n */\nexport function aiLayoutNeutralStarterRun(count: number, seed: string): AiLayoutPicturePhoto[] {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n return Array.from({ length: Math.max(0, count) }, (_, index) => {\n const photo = AI_LAYOUT_STARTER_PHOTOS[pool[index % pool.length] as StarterName]\n return { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n}\n\n/**\n * A neutral starter for each slot a library could not fill, where the site\n * has a library (AGL-3923): each at most once on a page, the hero served\n * first and each pool turned by the seed, and none for a portrait of the\n * business's people or a product, nor for a slot past the two. A slot given\n * none keeps the no-photo treatment.\n */\nexport function aiLayoutNeutralStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): Array<AiLayoutPicturePhoto | null> {\n const pool = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(seed))\n const order: AiLayoutPictureRole[] = ['hero', 'gallery', 'about']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot }) => !slot.people && !slot.product)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n served.forEach(({ index }, step) => {\n const name = pool[step]\n if (!name) return\n const photo = AI_LAYOUT_STARTER_PHOTOS[name]\n photos[index] = { src: photo.src, width: photo.width, height: photo.height, alt: photo.alt }\n })\n return photos\n}\n\n/** A text's words a photo's own words may share: lowercase, four letters or more. */\nconst wordsOf = (text: string): Set<string> =>\n new Set(\n text\n .toLowerCase()\n .split(/[^a-zÀ-ɏ]+/)\n .filter((word) => word.length >= 4),\n )\n\n/**\n * The job's own photos for the slots a library could not fill (AGL-3988):\n * the photos THIS job already placed on this site — on its other pages, on\n * its datasets' records, and on this page — which were each found for the\n * site's own world, before any starter. The beta.270 Sugar Hollow\n * Photography start (2026-10-19) opened a wedding photographer's home page\n * on the starters' desk and an office team, and filed five wedding galleries\n * under a workbench, a windowsill and the same office, while the library\n * had already given the job three wedding and family photos.\n *\n * Each slot `need` names takes the one that fits it best: one this page does\n * not show yet before one it does, a wide photo for a hero, the shape\n * nearest its frame, and the most words shared with what the slot says it\n * shows; never the photo of the slot beside it. A product's photo and a\n * portrait of the business's people are of one named thing, which no other\n * photo is, so they take none. Among equals the seed picks, so one job\n * always picks the same. `null` for a slot given none. Pure.\n *\n * `repeats` is a site start's (AGL-3988), where a photo shown twice on one\n * page reads as a defect: `'never'` when its page is built, where a slot no\n * photo the page does not show yet fits keeps a starter for the start's\n * late photo pass; `'last'` in that pass, where the other choice is a starter\n * of another business, so a photo the page does not show comes first, then\n * one it shows elsewhere, and the photo beside the slot only after that.\n */\nexport function aiLayoutJobPhotoPicks(\n slots: readonly AiLayoutPictureSlot[],\n need: readonly boolean[],\n jobPhotos: readonly AiLayoutPicturePhoto[],\n placed: ReadonlyArray<AiLayoutPicturePhoto | null | undefined>,\n seed: string,\n options: { repeats?: 'never' | 'last' } = {},\n): Array<AiLayoutPicturePhoto | null> {\n const seen = new Set<string>()\n const pool = jobPhotos.filter((photo) => {\n if (!photo.src || !(photo.width > 0) || !(photo.height > 0) || seen.has(photo.src)) return false\n seen.add(photo.src)\n return true\n })\n const picks: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n if (!pool.length) return picks\n const shown = slots.map((_slot, index) => placed[index]?.src || '')\n const order: AiLayoutPictureRole[] = ['hero', 'about', 'gallery']\n const served = slots\n .map((slot, index) => ({ slot, index }))\n .filter(({ slot, index }) => need[index] && !slot.product && !slot.people)\n .sort((a, b) => order.indexOf(a.slot.role) - order.indexOf(b.slot.role) || a.index - b.index)\n const turn = aiLayoutSeedNumber(seed)\n for (const { slot, index } of served) {\n const beside = new Set([shown[index - 1], shown[index + 1]].filter(Boolean))\n const says = wordsOf(slot.alt)\n let best: { photo: AiLayoutPicturePhoto; score: number; rank: number } | null = null\n for (const [at, photo] of pool.entries()) {\n const nextTo = beside.has(photo.src)\n const uses = shown.filter((src) => src === photo.src).length\n if (nextTo && options.repeats !== 'last') continue\n if (uses && options.repeats === 'never') continue\n const aspect = photo.width / photo.height\n const shared = [...wordsOf(photo.alt ?? '')].filter((word) => says.has(word)).length\n const score =\n (nextTo ? -10_000 : 0) -\n (options.repeats === 'last' ? 1_000 : 100) * uses +\n (slot.role === 'hero' && aspect >= 1.3 ? 20 : 0) -\n 10 * Math.abs(Math.log(aspect / (slot.aspect > 0 ? slot.aspect : 1))) +\n 4 * shared\n // The seed turns the pool, so equals are taken in a per-job order.\n const rank = (at - (turn % pool.length) + pool.length) % pool.length\n if (!best || score > best.score || (score === best.score && rank < best.rank)) best = { photo, score, rank }\n }\n if (!best) continue\n picks[index] = { ...best.photo }\n shown[index] = best.photo.src\n }\n return picks\n}\n\n/**\n * The page with each slot filled by its photo: the `image` given its source,\n * its intrinsic size and how it loads (the hero eagerly, as the starter home\n * page loads its own), and the placeholder icon taken out of the frame. The\n * alt text is kept as the model wrote it unless the photo brings its own\n * words. A slot given no photo is left as the compiler wrote it.\n */\nexport function aiLayoutPlacePictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n): NodesMap {\n const next = { ...(nodes as unknown as Record<string, Record<string, unknown>>) }\n // Only the page's first picture loads eagerly (rule 16, AGL-3660): a hero\n // of several photos loaded them all eagerly, and a live Portfolio page was\n // refused for it (2026-10-10). The slots are in document order; every later\n // picture leaves `loading` unset, which the renderer loads when reached.\n const lead = slots.findIndex((_, index) => Boolean(photos[index] && next[slots[index].imageId]))\n slots.forEach((slot, index) => {\n const photo = photos[index]\n const node = next[slot.imageId]\n if (!photo || !node) return\n const props = { ...((node['props'] as Record<string, unknown> | undefined) ?? {}) }\n delete props['loading']\n // A photo placed is described by its words, never hidden from a screen\n // reader: a stand-in was decorative only while it showed nothing (AGL-3856).\n // One that brings no words of its own, such as an owner's upload with no\n // alt text, keeps the slot's (AGL-3979): a picture a visitor sees always\n // has a description.\n const alt = photo.alt?.trim() || (typeof props['alt'] === 'string' ? props['alt'].trim() : '')\n if (alt) delete props['decorative']\n next[slot.imageId] = {\n ...node,\n props: {\n ...props,\n src: photo.src,\n intrinsicWidth: photo.width,\n intrinsicHeight: photo.height,\n ...(alt ? { alt } : {}),\n ...(index === lead && slot.role === 'hero' ? { loading: 'eager' } : {}),\n },\n }\n if (slot.iconId && slot.frameId && next[slot.frameId]) {\n const frame = next[slot.frameId]\n next[slot.frameId] = {\n ...frame,\n nodes: ((frame['nodes'] as string[] | undefined) ?? []).filter((child) => child !== slot.iconId),\n }\n delete next[slot.iconId]\n }\n })\n return next as unknown as NodesMap\n}\n\n/** Whether a node is a grid row: a `muiGrid` container. */\nconst isGridRow = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && (node['props'] as Record<string, unknown> | undefined)?.['container'] === true\n\n/** Whether a node is a column of a grid row: a sized `muiGrid` item. */\nconst isGridColumn = (node: Record<string, unknown> | undefined) =>\n node?.['componentId'] === 'muiGrid' && !isGridRow(node)\n\n/**\n * The page without the pictures left with no photo (AGL-3856). A slot whose\n * photo is missing loses its image, its icon and its frame where the frame\n * holds nothing else, and so does every wrapper that holds nothing once it\n * is gone, up to its section. A cover's black scrim over nothing read as a\n * solid black block (the beta.261 Synchronicity Gym hero), and an empty\n * rounded gray card as a broken picture (the beta.262 Adult Hybrid Learning\n * Center hero). A column of a split row taken out leaves the words' column\n * the row's full width. A cover's section stays pinned to the site's dark\n * scheme, which paints that scheme's background and text colors, so the\n * words written for a dark photo stay readable. A slot a photo fills, a\n * starter's included, is kept.\n */\nexport function aiLayoutDropEmptyPictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n sectionIds: readonly string[] = [],\n): NodesMap {\n const map = nodes as unknown as Record<string, Record<string, unknown>>\n const empty = slots.filter((slot, index) => {\n if (photos[index]?.src) return false\n const src = (map[slot.imageId]?.['props'] as Record<string, unknown> | undefined)?.['src']\n return Boolean(map[slot.imageId]) && !(typeof src === 'string' && src.trim())\n })\n if (!empty.length) return nodes\n const next = { ...map }\n const children = (id: string) => (next[id]?.['nodes'] as string[] | undefined) ?? []\n const parentOf = (id: string) => Object.keys(next).find((key) => children(key).includes(id)) ?? null\n const remove = (id: string) => {\n const parent = parentOf(id)\n if (parent) next[parent] = { ...next[parent], nodes: children(parent).filter((child) => child !== id) }\n delete next[id]\n return parent\n }\n const sections = new Set(sectionIds)\n for (const slot of empty) {\n const own = new Set([slot.imageId, slot.iconId].filter(Boolean) as string[])\n const frameHoldsOnly = Boolean(slot.frameId && next[slot.frameId] && children(slot.frameId).every((child) => own.has(child)))\n let parent = remove(slot.imageId)\n if (slot.iconId && next[slot.iconId]) remove(slot.iconId)\n if (!frameHoldsOnly) continue\n // The frame, then each wrapper it leaves empty: never a section, nor the page's root.\n let at: string | null = slot.frameId\n while (at && next[at] && !children(at).length && !sections.has(at) && parentOf(at) !== null) {\n const column = isGridColumn(next[at])\n parent = remove(at)\n at = column ? null : parent\n }\n // A split row left with one column gives its words the row's width.\n if (parent && isGridRow(next[parent])) {\n const [only, ...rest] = children(parent)\n if (only && !rest.length && isGridColumn(next[only])) {\n const column = next[only] as Record<string, unknown>\n const sx = { ...((column['sx'] as Record<string, unknown> | undefined) ?? {}) }\n delete sx['order']\n next[only] = {\n ...column,\n props: { ...((column['props'] as Record<string, unknown> | undefined) ?? {}), size: 'xs:12' },\n sx: Object.keys(sx).length ? sx : null,\n }\n }\n }\n }\n return next as unknown as NodesMap\n}\n\n/**\n * A source of photos found for the slots themselves — a stock search copied\n * into the site's library — answering per slot, in order: a photo; or\n * {@link AI_LAYOUT_NO_PHOTO} where the library answered and nothing fits,\n * which leaves the slot without a photo; or `null` where the library could\n * not be asked, which takes a starter photo. It may throw, and then every\n * slot takes a starter.\n */\nexport type AiLayoutPictureSource = (\n slots: readonly AiLayoutPictureSlot[],\n report?: AiLayoutPictureReport,\n) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>\n\n/**\n * What a source tells its caller beside its answers (AGL-3988): for each slot\n * whose fitting photo the library's image hosts would not hand over, the\n * picks it kept in hand and the search they came from, so the site start's\n * late photo pass can ask for them again once the burst is over. Keyed by\n * the slot itself, which every source hands on unchanged.\n */\nexport interface AiLayoutPictureReport {\n refused: Map<AiLayoutPictureSlot, AiStockRefusedPicks>\n}\n\n/**\n * Fills a stored page's empty picture slots (AGL-3660): from `source` where\n * one is given and it answers; a slot it answers with nothing fitting stays\n * without a photo and is taken out ({@link aiLayoutDropEmptyPictures}). A\n * slot it could not answer — the library down, out of time, or its photo not\n * copied — takes one of the photos the job already placed on this site or\n * this page just copied (`jobPhotos`, {@link aiLayoutJobPhotoPicks},\n * AGL-3988), and only where the job has none, a starter photo described by\n * its own words (AGL-3831). On a site start (`onStarters`), a photo the\n * page already shows is never placed again, a slot none of the others fits\n * yet keeps a neutral starter too, and `onStarters` is told of every\n * picture left on one, for the late photo pass. Never throws, and\n * never leaves a page worse than it came: a picture is decoration, and a\n * job never fails over one.\n */\nexport async function aiResolveLayoutPictures(\n nodes: NodesMap,\n input: {\n rootId: string\n sectionIds: readonly string[]\n sectionNames: readonly string[]\n /** A per-job word, so two sites do not open with the same photo. */\n seed: string\n source?: AiLayoutPictureSource | null\n /**\n * The photos this job already placed on this site (AGL-3988): its other\n * pages' and its datasets' records', each found for the site's world.\n * Taken before any starter where `source` could not fill a slot.\n */\n jobPhotos?: readonly AiLayoutPicturePhoto[]\n /** The site's language (AGL-3750); a starter photo's English words are not its own. */\n language?: string\n /**\n * Told the pictures left on a starter where the site has a library\n * (AGL-3988), with the picks the image hosts refused for each: what the\n * site start's late photo pass fills from the job's own photos. Given\n * only where such a pass follows: a failed slot then never takes a photo\n * the page already shows, and one none of the job's other photos fits\n * yet keeps a neutral starter for that pass, rather than lose its frame.\n */\n onStarters?: (slots: AiSiteStarterSlot[]) => void\n },\n): Promise<NodesMap> {\n try {\n const slots = aiLayoutPictureSlots(nodes, input.rootId, {\n ids: input.sectionIds,\n names: input.sectionNames,\n })\n if (!slots.length) return nodes\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n const report: AiLayoutPictureReport = { refused: new Map() }\n if (input.source) {\n try {\n found = await input.source(slots, report)\n } catch (error) {\n console.warn('ai layout pictures: the stock source failed; the job’s own photos or starters fill the page', { error })\n found = []\n }\n }\n // The starters' words are English; a site in another language says it\n // shows an illustrative photo, true of any photo, as a stock photo's does.\n const otherLanguage = aiLayoutSpeaksOtherThanEnglish(input.language)\n const said = (photo: AiLayoutPicturePhoto): AiLayoutPicturePhoto =>\n otherLanguage ? { ...photo, alt: aiLayoutLabel('illustrativePhoto', input.language) } : photo\n /** The slots the source could not answer: no library, or it failed them. */\n const unanswered = slots.map((_slot, index) => !found[index])\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map((_slot, index) => {\n const answer = found[index]\n return answer?.src ? answer : null\n })\n // With a library, the job's own photos come before any starter (AGL-3988),\n // and a starter is only ever a neutral one (AGL-3923): the beta.267\n // Brightvale start, whose library refused its copies, showed a pottery\n // bench and a coffee shop's owner as a marketer's pictures. The job's\n // own are its other pages' and its records' photos, and the ones this\n // page just copied for its other slots: the beta.271 Laurel Fork home\n // page, the first page built, copied a wide wedding couple and a family\n // for two of its slots while its hero's picks were refused, and opened\n // on the starters' office team. A photo the page does not show yet is\n // still preferred, and never the one beside it.\n const own = input.source\n ? [...(input.jobPhotos ?? []), ...photos.filter((photo): photo is AiLayoutPicturePhoto => Boolean(photo))].filter(\n (photo) => photo.src && photo.width > 0 && photo.height > 0,\n )\n : []\n // On a site start (`onStarters`) a photo the page already shows is never\n // placed again here (AGL-3988): the beta.271 Laurel Fork home page opened\n // on the wedding couple its closing band shows. Such a slot keeps a\n // starter, and the start's late photo pass fills it.\n const lateStart = Boolean(input.source && input.onStarters)\n if (own.length) {\n aiLayoutJobPhotoPicks(slots, unanswered, own, photos, input.seed, lateStart ? { repeats: 'never' } : {}).forEach((pick, index) => {\n if (pick && unanswered[index]) photos[index] = pick\n })\n }\n // A failed slot takes a neutral starter only where the job had no photo\n // of its own before this page, as #1831 had it. On a site start, where\n // the job has photos of its own, every failed slot none of them fits yet\n // keeps one: that start's late photo pass gives it one of the job's own\n // once every page is built (AGL-3988), told of it by `onStarters`.\n // Anywhere else it keeps the no-photo treatment. With no library, every\n // starter, as before.\n const hadOwn = (input.jobPhotos ?? []).some((photo) => photo.src && photo.width > 0 && photo.height > 0)\n const starters = !input.source\n ? aiLayoutStarterPhotos(slots, input.seed)\n : lateStart && own.length\n ? aiLayoutNeutralStarterRun(slots.length, input.seed).map((photo, index) =>\n slots[index]?.people || slots[index]?.product ? null : photo,\n )\n : hadOwn && !lateStart\n ? []\n : aiLayoutNeutralStarterPhotos(slots, input.seed)\n starters.forEach((photo, index) => {\n if (photo && unanswered[index] && !photos[index]) photos[index] = said(photo)\n })\n // The page floor (AGL-3923; Zach, 2026-10-12: a starter photo before no\n // photo at all): a page the library answered that would show no picture\n // keeps one, in its hero, else its first slot: one of the job's own\n // photos where it has any (AGL-3988), else a neutral starter.\n if (input.source && !photos.some((photo) => photo?.src)) {\n const at = Math.max(0, slots.findIndex((slot) => slot.role === 'hero'))\n const lead = { ...(slots[at] as AiLayoutPictureSlot), product: undefined, people: undefined }\n const [pick] = aiLayoutJobPhotoPicks([lead], [true], own, [null], input.seed)\n const [floor] = rotate(NEUTRAL_STARTERS, aiLayoutSeedNumber(input.seed))\n const starter = floor ? AI_LAYOUT_STARTER_PHOTOS[floor] : null\n if (pick) photos[at] = pick\n else if (starter) photos[at] = said({ src: starter.src, width: starter.width, height: starter.height, alt: starter.alt })\n }\n // The pictures left on a starter, for the late photo pass (AGL-3988).\n if (input.source && input.onStarters) {\n const left = slots.flatMap((slot, index): AiSiteStarterSlot[] => {\n if (!aiSiteIsStarterSrc(photos[index]?.src)) return []\n const refused = report.refused.get(slot)\n return [{ image: slot.imageId, words: slot.alt, role: slot.role, aspect: slot.aspect, ...(refused ? { refused } : {}) }]\n })\n if (left.length) input.onStarters(left)\n }\n return aiLayoutDropEmptyPictures(aiLayoutPlacePictures(nodes, slots, photos), slots, photos, input.sectionIds)\n } catch (error) {\n console.error('ai layout pictures: the page keeps its empty picture slots', { error })\n return nodes\n }\n}\n"],"names":["DEFAULT_SITE_IMAGES","aiSiteIsStarterSrc","aiLayoutLabel","aiLayoutSpeaksOtherThanEnglish","PEOPLE","ABOUT","aspectOf","value","byBreakpoint","breakpoint","ratio","match","exec","String","Number","isFinite","COVER_ASPECT","slotAspect","image","frame","own","sx","aiLayoutPictureRole","input","sectionIndex","test","sectionName","alt","aiLayoutPictureSlots","nodes","rootId","sections","map","slots","seen","Set","visit","id","parentId","node","has","add","index","ids","indexOf","here","componentId","src","props","trim","undefined","iconId","find","child","push","imageId","frameId","aspect","standIn","role","names","AI_LAYOUT_STARTER_PHOTOS","AI_LAYOUT_NO_PHOTO","Object","freeze","width","height","STARTER_POOLS","hero","about","gallery","ALL_STARTERS","keys","aiLayoutSeedNumber","seed","hash","length","charCodeAt","Math","imul","rotate","list","by","start","slice","aiLayoutStarterPhotos","turn","order","served","slot","sort","a","b","placedAt","Map","forEach","step","unused","pool","rest","filter","name","includes","candidates","people","candidate","beside","Boolean","get","set","photo","NEUTRAL_STARTERS","aiLayoutNeutralStarterRun","count","Array","from","max","_","aiLayoutNeutralStarterPhotos","product","photos","wordsOf","text","toLowerCase","split","word","aiLayoutJobPhotoPicks","need","jobPhotos","placed","options","picks","shown","_slot","says","best","at","entries","nextTo","uses","repeats","shared","score","abs","log","rank","aiLayoutPlacePictures","next","lead","findIndex","intrinsicWidth","intrinsicHeight","loading","isGridRow","isGridColumn","aiLayoutDropEmptyPictures","sectionIds","empty","children","parentOf","key","remove","parent","frameHoldsOnly","every","column","only","size","aiResolveLayoutPictures","sectionNames","found","report","refused","source","error","console","warn","otherLanguage","language","said","unanswered","answer","lateStart","onStarters","pick","hadOwn","some","starters","floor","starter","left","flatMap","words"],"mappings":"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"}
@@ -21,6 +21,7 @@ import type { AiBuildPlan } from './ai-build-plan';
21
21
  import type { AiFreeCreditsLeft } from './ai-site-job';
22
22
  import type { AiJobCreditsConfirmation } from './ai-credit-estimate';
23
23
  import type { AiSiteImportState } from './ai-site-import';
24
+ import type { AiSitePhotos } from './ai-site-photos';
24
25
  import type { AiSiteInspiration } from '../jobs/ai-site-inspiration-signals';
25
26
  /**
26
27
  * AI generation jobs (AGL-2904).
@@ -559,6 +560,15 @@ export interface AiJob {
559
560
  * the import step returns; absent on every other kind.
560
561
  */
561
562
  siteImport?: AiSiteImportState | null;
563
+ /**
564
+ * A `site` job's pictures left on a starter photo (AGL-3988): each page
565
+ * picture and dataset record its library could not fill in its own pass,
566
+ * with the picks the image hosts refused. Written only by the machine's
567
+ * `recordStep`, from what the site step returns; emptied by its late photo
568
+ * pass before the site is published. Never on the wire. Absent on every
569
+ * other kind.
570
+ */
571
+ sitePhotos?: AiSitePhotos | null;
562
572
  }
563
573
  /** When the AI jobs beat removed a guided start's empty site, and which (AGL-3936). */
564
574
  export interface AiJobSiteRemoved {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-jobs.types.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type { AiEffort } from '../providers/contract'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiBuildPlan } from './ai-build-plan'\nimport type { AiFreeCreditsLeft } from './ai-site-job'\nimport type { AiJobCreditsConfirmation } from './ai-credit-estimate'\nimport type { AiSiteImportState } from './ai-site-import'\nimport type { AiSiteInspiration } from '../jobs/ai-site-inspiration-signals'\n\n/**\n * AI generation jobs (AGL-2904).\n *\n * A job is one customer brief carried through one or more model steps by a\n * Firestore state machine, so that work longer than a request can survive\n * the request: the console route runs what it can inline, and the platform\n * job beat resumes whatever is left. The document is the whole state — no\n * process holds anything the next beat cannot read back.\n *\n * ── Outputs are drafts, never a publish ──────────────────────────────────\n *\n * Every output a job produces is a NEW draft or a new unpublished version of\n * an existing resource. A job never flips a version pointer, never registers\n * a route and never sends anything; a person opens the draft the job names\n * and publishes it through the door that already exists for that resource.\n * That is what lets a job run with no visitor, no session and no confirm\n * gate — the thing it writes cannot be seen by anyone but the workspace\n * until a member chooses otherwise.\n *\n * ── The lease ────────────────────────────────────────────────────────────\n *\n * A step runs under a lease: an owner id and an `until` instant. Two beats\n * fire a minute apart against a 120 s function timeout, and a console route\n * may still be running the first step when the first beat looks, so the\n * lease is what stops the same step being run — and metered — twice. A\n * claim is a transaction that refuses while another owner's lease is live;\n * an expired lease is simply claimable, which is also how a step abandoned\n * by a frozen process is recovered.\n */\n\n/**\n * What a job produces. `text` and `theme` have runners; every other kind is\n * registered and refuses with \"not available yet\" until its own issue lands,\n * so a kind's presence in the union says the job model accepts it, not that\n * a runner exists.\n */\nexport type AiJobKind =\n | 'page'\n | 'component'\n | 'layout'\n | 'template'\n | 'edit'\n | 'seo'\n | 'site'\n | 'email'\n | 'campaign'\n | 'form'\n | 'experiment'\n | 'insight'\n | 'products'\n | 'crm'\n | 'onboarding'\n | 'workflow'\n | 'logic'\n | 'text'\n | 'theme'\n | 'build'\n /** A change to a site's header or footer menu, made by code (AGL-3728). */\n | 'navigation'\n /** A copy of something a site already has, made by core's duplicate (AGL-3740). */\n | 'duplicate'\n /** A change to a site's settings, made by code and saved live (AGL-3748). */\n | 'settings'\n /** Folders, tags, alt text and names in a site's media library, made by code and saved live (AGL-3752). */\n | 'media'\n /** A change to a site's analytics and advertising tags, made by code and saved live (AGL-3751). */\n | 'tracking'\n /** A person's own website brought in, page by page, in reviewed waves (AGL-3976). */\n | 'import'\n\n/** The union as a value, so a route can validate a body against it. */\nexport const AI_JOB_KINDS: readonly AiJobKind[] = [\n 'page',\n 'component',\n 'layout',\n 'template',\n 'edit',\n 'seo',\n 'site',\n 'email',\n 'campaign',\n 'form',\n 'experiment',\n 'insight',\n 'products',\n 'crm',\n 'onboarding',\n 'workflow',\n 'logic',\n 'text',\n 'theme',\n 'build',\n 'navigation',\n 'duplicate',\n 'settings',\n 'media',\n 'tracking',\n 'import',\n]\n\n/**\n * `queued` — waiting for a runner; `running` — a step holds the lease;\n * `needs_input` — the job stopped for something only the workspace can\n * change (credits, a cap, a switch), and the beat tries it again once that\n * may have changed — or, carrying `input`, for an answer only the person can\n * give (AGL-3978), which the beat leaves alone; `needs_review` — the job stopped for a person's decision\n * (AGL-2935): its plan is ready to confirm, or an answer broke a building\n * rule on its re-ask. No timer can make that decision, so neither the beat\n * nor anything but that person resumes it. The last three are terminal.\n */\nexport type AiJobStatus =\n | 'queued'\n | 'running'\n | 'needs_input'\n | 'needs_review'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport const AI_JOB_TERMINAL_STATUSES: readonly AiJobStatus[] = [\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport type AiJobStepStatus = 'pending' | 'running' | 'done' | 'failed'\n\n/** The step a planned kind runs first (AGL-2935): its plan, before anything is built. */\nexport const AI_JOB_PLAN_STEP = 'plan'\n\nexport interface AiJobStep {\n /** Stable within the job (`plan`, `draft`, `generate`), unique per job. */\n name: string\n status: AiJobStepStatus\n startedAt?: ITimestamp | null\n endedAt?: ITimestamp | null\n /** Credits this step has actually cost, at the plan's credit rate. */\n creditsSpent: number\n /** Customer-safe only; provider detail goes to the server log. */\n error?: string | null\n /**\n * How many times a runner has been handed this step. A retryable provider\n * failure hands the step back as `pending`; the count is what stops a\n * provider outage re-running a step forever.\n */\n attempts?: number\n /**\n * Passes of a step that works through a list (AGL-2910): each one a\n * recorded, metered exchange that asked to continue. Attempts count again\n * from zero after a pass; this count is what bounds the step.\n */\n passes?: number\n /**\n * What the step's model calls cost in tokens and time (AGL-2937), summed\n * over every run that reached the provider. Absent on a step none has.\n */\n tokens?: AiJobStepTokens\n /**\n * The ids the step writes its drafts under, by draft (AGL-3079): minted\n * when the job is created, so every run of the step names the same drafts.\n * Absent on a step that writes none; a step that records none names its\n * drafts by the job's own id.\n */\n draftIds?: Partial<Record<AiJobDraftSlot, string>>\n}\n\n/**\n * A draft a job's kind always writes (AGL-3079): a screen, layout, template,\n * form or component; an email design; a campaign; an automation.\n */\nexport type AiJobDraftSlot =\n | 'screen'\n | 'layout'\n | 'template'\n | 'form'\n | 'component'\n | 'email'\n | 'campaign'\n | 'workflow'\n\n/**\n * A step's measure (AGL-2937): the four token counts the meter prices, the\n * time spent in the runner, and the model and thinking effort of the last\n * run. The machine writes it beside `creditsSpent`, so what a kind of step\n * costs in tokens is read off the job rather than inferred from the month.\n */\nexport interface AiJobStepTokens {\n /** Prompt tokens sent uncached. */\n input: number\n /** Prompt tokens read from the prompt cache. */\n cachedRead: number\n /** Prompt tokens written to the prompt cache. */\n cacheWrite: number\n /** Tokens generated, thinking included. */\n output: number\n /** The model the last run was served by. */\n model: string | null\n /** The thinking effort the last run asked for; `null` when it named none. */\n effort: AiEffort | null\n /** Milliseconds spent in the runner, summed over runs. */\n latencyMs: number\n /** Runs that reached the provider. */\n runs: number\n /**\n * Why the step's latest runs stopped (AGL-3042), oldest first and the last\n * run last: each run's stop reason and the tokens it generated, so a pass\n * cut off at its ceiling reads off the job rather than out of the sums. At\n * most `AI_JOB_STEP_LAST_RUNS` are kept; absent on a step measured before.\n */\n lastRuns?: AiJobStepRunStop[]\n}\n\n/** One run of a step, by why it stopped (AGL-3042). */\nexport interface AiJobStepRunStop {\n /**\n * The stop reason of the run's last model call — `tool_use` or `end_turn`\n * for an answer, `max_tokens` for one cut off at its ceiling, `refusal` —\n * and `null` when the provider named none.\n */\n stopReason: string | null\n /** Tokens the run generated, over every model call it made. */\n output: number\n}\n\nexport type AiJobOutputResource =\n | 'screen'\n | 'reusableComponent'\n | 'layout'\n | 'template'\n | 'form'\n | 'emailScreen'\n | 'campaign'\n | 'product'\n | 'experiment'\n | 'workflow'\n | 'text'\n | 'theme'\n | 'seo'\n | 'insight'\n | 'crm'\n /** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */\n | 'draft'\n | 'logic'\n | 'orgAutomation'\n /** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */\n | 'entry'\n /** A site's settings a build changed (AGL-3748): saved live, on the host document. */\n | 'settings'\n /** Files in a site's media library a build organized (AGL-3752): saved live, on their documents. */\n | 'media'\n /** A site's tracking tags a build changed (AGL-3751): saved live, on the host document. */\n | 'tracking'\n\n/**\n * One thing a job wrote. Addressed by resource and id so the console can\n * build the \"open draft\" link without knowing what the runner did; `text`\n * has no document of its own, so its body rides on the output itself.\n *\n * A `theme` output is a proposal rather than a document (AGL-2938). A site's\n * theme is fields on its host document, and writing those fields is the save\n * the Theme section's editor performs, which a job never does. So the change\n * set rides on the output as `proposal`, and a person puts it in the editor\n * and saves it there, or does not.\n *\n * An `seo` output is a proposal rather than a document (AGL-2910): search\n * listing values for a page or a product, or a site audit's findings and\n * fixes. A screen's listing is served from the screen document itself, so a\n * job never writes it; the values ride on the output as `proposal`, a person\n * puts them in the editor and saves them there, and an audit's content fixes\n * become new versions only when a person applies them.\n *\n * A `crm` output is a proposal too (AGL-2917): a record's summary and next\n * step, an email draft, or an import's column matches. The CRM's own task\n * form, stage route, composer and import drawer are the writes, and a person\n * makes each of them, or does not.\n *\n * A `logic` output is a proposal too (AGL-3603): a site function or variable\n * the Functions & Variables editor opens unsaved. Saving it is the editor's\n * write, which a person makes, or does not.\n *\n * An `orgAutomation` output is a proposal too (AGL-3603): one of the\n * workspace's automations, which the Org automations editor opens unsaved and\n * switched off. `hostId` is `null`: it belongs to no one site.\n */\nexport interface AiJobOutput {\n resource: AiJobOutputResource\n id: string\n /** The unpublished version the job created, where the resource has versions. */\n versionId?: string | null\n /** `null` for an output that belongs to the org rather than one site. */\n hostId: string | null\n /**\n * The site's subdomain, which is what a console URL names a site by: the\n * `[host]` segment resolves by subdomain, so a link built from `hostId`\n * names no site the console can open.\n */\n hostSubdomain?: string | null\n label: string\n /** The body of a `text` output. Absent on every other resource. */\n text?: string\n /**\n * The change set of an output a person applies rather than opens — a\n * `theme` or `seo` proposal — in the shape its job kind's runner defines.\n */\n proposal?: Record<string, unknown>\n /**\n * What the person decides next about this output, in customer-safe words:\n * a routing the draft could not store, or part of the brief it could not\n * build. Absent when there is nothing to decide.\n */\n note?: string | null\n /**\n * What a first visit is estimated to transfer, as the doctrine measured\n * the generated document (AGL-2935) — the weight the proposal shows before\n * anything is applied. Absent where the output is not a page's document.\n */\n load?: AiLoadEstimate | null\n /**\n * For a `draft` output (AGL-3616): the resource whose owner wrote it, as\n * its writer is registered on the resource-draft seam.\n */\n draftResource?: string\n}\n\nexport interface AiJobLease {\n /** The process that holds the step: a route request id or a beat id. */\n owner: string\n until: ITimestamp\n}\n\n/** Why a job stopped for a person (AGL-2935). */\nexport type AiJobReviewReason =\n /** The plan step proposed a plan; generation waits for it to be confirmed. */\n | 'plan'\n /** An answer broke a building rule, and so did the answer to the re-ask. */\n | 'doctrine'\n /**\n * The site is at an allowance its plan sets for what the step writes, such\n * as its shared layouts or its templates (AGL-2909). Freeing one, or a plan\n * that holds more, is the member's decision; trying again runs the step\n * again.\n */\n | 'limit'\n /**\n * A site import's wave is in (AGL-3976): its pages are drafts for the\n * person to review, and the next wave starts only when they say so. Or the\n * import has read the site and waits for the first wave to be started.\n */\n | 'wave'\n /**\n * A site import's next wave needs a current proof that the workspace owns\n * the site (AGL-3976): the proof is older than its 30 days, or was never\n * made. Verifying again and starting the wave runs it.\n */\n | 'ownership'\n\n/**\n * A building rule an answer broke, as the job keeps it: the number, the code,\n * the sentence, and where in the answer it was broken (AGL-3078).\n */\nexport interface AiJobRuleFinding {\n rule: number | null\n code: string\n /** Customer-safe. */\n message: string\n /** The nodes the finding names, by the ids the refused answer gave them. Absent when it names none. */\n nodeIds?: string[]\n /** The plan entries or answer fields it names, as `screens[0].sections[2]`. Absent when it names none. */\n paths?: string[]\n}\n\n/**\n * One node of the parts of a refused answer its findings name (AGL-3078), as\n * the review keeps it: the node's place, its element and the names of what it\n * sets, and never its copy. A Grid keeps the values of its layout props as\n * they were written, so a review can tell a shape the rules refuse from one\n * the model was asked to mend and did not.\n */\nexport interface AiJobReviewOutlineNode {\n /** The node's id, as the refused answer wrote it. */\n id: string\n /** How far below a node a finding names it sits: 0 for that node. */\n depth: number\n componentId: string\n /** The props it sets, by name only. */\n props: string[]\n /** The `sx` keys it sets, by name only; absent when it sets none. */\n sx?: string[]\n /** A Grid's layout props, `container`, `size`, `direction`, `spacing` and the rest, as written. */\n grid?: Record<string, string | number | boolean | null>\n /** Its children's element ids, in order. */\n children: string[]\n}\n\nexport interface AiJobReview {\n reason: AiJobReviewReason\n /** Customer-safe: what the person is asked to decide. */\n message: string\n /** The rules the last answer broke; empty for a plan. */\n findings: AiJobRuleFinding[]\n /**\n * The parts of the refused answer its findings name, each node with the\n * nodes below it (AGL-3078); absent when they name none. Kept with the job\n * for diagnosis, and shown to staff.\n */\n outline?: AiJobReviewOutlineNode[]\n /**\n * The checks' own sentence, where `message` was written for the member\n * instead (AGL-3594): rule numbers and all, for staff. Its presence is what\n * tells the drawer the findings are staff reading too.\n */\n detail?: string\n /**\n * Why trying again cannot work yet (AGL-3594) — the Free allowance left is\n * less than the step can cost — or absent when it can. The button is drawn\n * disabled with this beside it.\n */\n retryRefusal?: string\n /**\n * What a Free workspace had left when a build's plan was kept (AGL-3722),\n * so its plan card says, before Confirm, whether the build fits and offers\n * the go-ahead or a smaller first build when it does not. The confirm door\n * reads what is left again; this is what the card shows.\n */\n freeCredits?: AiFreeCreditsLeft\n}\n\n/**\n * One answer a job waits for (AGL-3978): an id only the person has, which the\n * operation that needs it describes — a tracking change's Meta pixel ID.\n */\nexport interface AiJobInputField {\n /** The plan item the answer fills, by its name in the plan. */\n item: string\n /** The item's argument the answer is written to. */\n arg: string\n /** The field's label, naming the provider: \"Meta pixel ID\". */\n label: string\n /** What an answer looks like, in the words of the card that sets it: \"8–20 digits\". */\n shape: string\n /** The pattern an answer is held to, as a RegExp's source. */\n pattern: string\n}\n\n/** What a job parked `needs_input` for a person asks (AGL-3978). */\nexport interface AiJobInputRequest {\n /** Customer-safe: the question, in the operation's own words. */\n message: string\n fields: AiJobInputField[]\n}\n\nexport type AiJobPlanStatus = 'proposed' | 'confirmed'\n\n/** The plan a job builds from, as the job keeps it once its plan step proposed it. */\nexport interface AiJobPlan extends AiBuildPlan {\n status: AiJobPlanStatus\n /** Names for the inventory ids the plan references, read when it was made. */\n labels: Record<string, string>\n proposedAt: ITimestamp\n confirmedAt: ITimestamp | null\n /** The member who confirmed it. */\n confirmedBy: string | null\n /**\n * A digest of the whole request that produced this plan (AGL-2937): the\n * job's kind, site, brief and scalar inputs, the model that answered, and\n * the prompt as it was rendered — the site inventory included. A later job\n * whose request hashes the same reuses this plan rather than paying for the\n * same answer again. A hash of the request, never of the answer, and no\n * part of the brief is recoverable from it.\n */\n key?: string\n /** The job this plan was reused from, when it was not asked for again. */\n reusedFrom?: string\n /**\n * What a site start read of the sites the person likes (AGL-3975): signals\n * only, each from a closed set or a hex color, which the plan was told the\n * structure of and the look pins the style of. Absent without them.\n */\n inspiration?: AiSiteInspiration\n}\n\n/**\n * Where one item of a `build` job stands (AGL-3616). A build settles item by\n * item: a failed item gives back its own spend and the rest of the plan still\n * runs, so the job keeps one row per item, written only by the machine's\n * `recordStep` — in the same transaction as the spend it accounts for — and\n * by the resume door's retry.\n */\nexport type AiJobItemStatus = 'pending' | 'running' | 'succeeded' | 'degraded' | 'failed' | 'skipped'\n\n/** Why an item failed: a give-back reason when it was ours, else the model's refusal or a review. */\nexport type AiJobItemFailureReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n | 'refused'\n | 'review'\n\nexport interface AiJobItemFailure {\n /** Whether the failure was on our side: only then is the item's spend given back. */\n ours: boolean\n reason: AiJobItemFailureReason\n /** Customer-safe. */\n message: string\n /**\n * The checks' own sentence behind a `doctrine-refused` failure (AGL-3596),\n * rule numbers and all: kept on the job for staff and debugging, never on\n * the wire a customer reads.\n */\n detail?: string\n}\n\nexport interface AiJobItemLedger {\n /** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */\n slot: string\n /** The capability operation that builds it: `page`, `form`, or an owner's op. */\n op: string\n /** What a person reads: the page's title, the creation's or item's name. */\n label: string\n status: AiJobItemStatus\n /** Bumped by Try again; the refund key names it, so each attempt gives back once. */\n attempt: number\n /** What this item's passes cost, every attempt. */\n creditsSpent: number\n /** What this attempt's passes cost so far: what a failure of this attempt gives back. */\n attemptCredits?: number\n /** What was given back for this item's failures. */\n creditsRefunded: number\n /** The key the latest give-back for this item was made under. */\n refundKey?: string\n failure?: AiJobItemFailure | null\n /** The ids of the outputs this item produced. */\n outputs: string[]\n /** The slots whose failure changed how this item was built. */\n degradedBy?: string[]\n /** Customer-safe: what the person should know about this item. */\n note?: string | null\n /**\n * When this attempt settled, as an ISO string (AGL-3596): the moment the\n * next item started, which is what its progress row counts from. Absent\n * while the item is open, and on a ledger written before it was kept.\n */\n settledAt?: string | null\n}\n\n/** Whether a build's own planning was charged or given back (AGL-3616). */\nexport interface AiJobOrchestration {\n /** What the plan step spent. */\n creditsSpent: number\n /** `charged` once anything was delivered; `refunded` when nothing was. */\n settled: 'pending' | 'charged' | 'refunded'\n}\n\nexport interface AiJob {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n status: AiJobStatus\n /** The customer's brief, verbatim. Expires with the document. */\n brief: string\n /** Kind-specific inputs the runner reads (a screen id to edit, a tone). */\n inputs: Record<string, unknown>\n /**\n * The model the creator picked for the job's steps (AGL-2942), or `null`\n * for Auto. Each step honors it only where the plan and the allotment\n * allowlists allow it, and runs on the routing table otherwise.\n */\n model?: string | null\n steps: AiJobStep[]\n outputs: AiJobOutput[]\n /**\n * The nominal credit figure held against the job while steps are\n * outstanding. The meter's real bound is the per-step message reservation\n * and the org's monthly ceiling; this is the number the console shows so\n * a running job is not read as free.\n */\n creditsReserved: number\n creditsSpent: number\n /**\n * Credits the job gave back because it failed on our side (AGL-3594),\n * summed over its give-backs; absent when it gave none. `creditsSpent`\n * stays what ran, so what the job cost is the one less the other.\n */\n refundedCredits?: number\n /** Why the latest give-back was made (`AiJobRefundReason`). */\n refundReason?: string\n /** How many give-backs the job has had: the next one's key. */\n refunds?: number\n createdBy: string\n createdAt: ITimestamp\n updatedAt: ITimestamp\n /** TTL — the brief is verbatim customer text and expires like an exchange. */\n expiresAt: ITimestamp\n /** Customer-safe only. */\n error?: string | null\n lease?: AiJobLease | null\n /** The plan the job builds from, once its plan step proposed one. */\n plan?: AiJobPlan | null\n /** What the person is asked to decide while the job is `needs_review`. */\n review?: AiJobReview | null\n /**\n * What the job waits for while it is `needs_input` for a person rather than\n * the meter (AGL-3978): the fields only they can fill, an id the planner\n * must never make up. Its plan and the plan's review are kept beside it,\n * and the answer returns the job to that review.\n */\n input?: AiJobInputRequest | null\n /**\n * What a person applied from the job's outputs (AGL-2910). Written by the\n * door that applied them, never by a step runner, and absent until then.\n */\n applied?: AiJobApplied | null\n /**\n * What a guided site start put live when it finished (AGL-3596): written\n * once by the site step's last pass, never by a person. Absent on every\n * other job, which leaves its pages as drafts.\n */\n sitePublish?: AiJobSitePublish | null\n /** A `build` job's items, one row each (AGL-3616); absent on every other kind. */\n items?: AiJobItemLedger[] | null\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration | null\n /**\n * A Free job started past what was left (AGL-3722): the person chose to\n * build what fits, knowing it pauses where the credits run out. Who, when,\n * what was left and the range they were shown; absent on every job that fit.\n */\n creditsConfirmed?: AiJobCreditsConfirmation | null\n /**\n * A person asked to stop the job (AGL-3616): written only by the cancel\n * door, in the transaction that decides it. A job no step holds ends\n * `canceled` in that same write; a job whose step holds a live lease keeps\n * running until the step finishes or aborts, and the machine ends it\n * `canceled` at its next write. Absent on a job nobody canceled.\n */\n cancelRequested?: AiJobCancelRequest | null\n /**\n * The empty site this guided start was for was removed (AGL-3936): the\n * job was cancelled or failed before it built anything, nobody chose what\n * to do next within the grace period, and the AI jobs beat deleted the\n * site. Written once, by the beat, after the site is gone. Absent on every\n * other job.\n */\n siteRemoved?: AiJobSiteRemoved | null\n /**\n * An `import` job's site, read and cut into waves (AGL-3976): what was\n * found, what is brought in in which wave, what was left out and why, and\n * each wave's state. Written only by the machine's `recordStep`, from what\n * the import step returns; absent on every other kind.\n */\n siteImport?: AiSiteImportState | null\n}\n\n/** When the AI jobs beat removed a guided start's empty site, and which (AGL-3936). */\nexport interface AiJobSiteRemoved {\n at: ITimestamp\n /** The site's name as it was, for a sentence that names it. */\n name: string | null\n}\n\n/** Who asked to cancel a job, and when (AGL-3616). */\nexport interface AiJobCancelRequest {\n at: ITimestamp\n /** The member or staff account that asked; `null` when nobody was named. */\n by: string | null\n}\n\n/**\n * A guided site start's publish (AGL-3596): the pages that went live, with\n * the address each answers, and the pages that stayed drafts, each with the\n * plain sentence for why. A page that stayed a draft never fails the job.\n */\nexport interface AiJobSitePublish {\n /** The site's live address, `https://<subdomain>.aglyn.app/`; `null` when the site has no subdomain. */\n liveUrl: string | null\n published: Array<{ id: string; label: string; path: string }>\n drafts: Array<{ id: string; label: string; reason: string }>\n}\n\n/**\n * The record of an apply (AGL-2910): what became a new unpublished version,\n * and what waits in its editor for a person to save.\n */\nexport interface AiJobApplied {\n at: ITimestamp\n /** The member who applied. */\n by: string\n /** The new version opened for each resource, by resource id. */\n versions: Record<string, string>\n /** Resource ids whose proposed values wait in their editor, unsaved. */\n staged: string[]\n}\n\n/** The plan on the wire: every instant an ISO string. */\nexport interface AiJobPlanSummary extends AiBuildPlan {\n status: AiJobPlanStatus\n labels: Record<string, string>\n proposedAt: string | null\n confirmedAt: string | null\n confirmedBy: string | null\n key?: string\n reusedFrom?: string\n}\n\n/**\n * The wire form the console reads: every instant an ISO string, the lease\n * reduced to whether a runner holds the job. The brief and the outputs\n * travel as they are — the reader is a member of the org that wrote them.\n */\nexport interface AiJobSummary {\n id: string\n orgId: string\n hostId: string | null\n kind: AiJobKind\n status: AiJobStatus\n brief: string\n /**\n * The batch a door created this job as one of (AGL-2911), read off the\n * job's inputs; `null` for a job created on its own. The console groups a\n * run of jobs by it, which is the one thing it cannot do from a list whose\n * rows carry no inputs — and the only input the wire form carries, because\n * a batch id names nothing the member wrote.\n */\n batch: string | null\n steps: Array<{\n name: string\n status: AiJobStepStatus\n startedAt: string | null\n endedAt: string | null\n creditsSpent: number\n error: string | null\n }>\n outputs: AiJobOutput[]\n creditsReserved: number\n creditsSpent: number\n /** Credits given back for a failure on our side (AGL-3594); 0 when none. */\n refundedCredits?: number\n createdBy: string\n createdAt: string\n updatedAt: string\n error: string | null\n running: boolean\n plan: AiJobPlanSummary | null\n review: AiJobReview | null\n /** The fields a `needs_input` job waits for a person to fill (AGL-3978); absent otherwise. */\n input?: AiJobInputRequest | null\n /** What a person applied from the outputs (AGL-2910); instants as ISO strings. */\n applied?: {\n at: string | null\n by: string\n versions: Record<string, string>\n staged: string[]\n } | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: AiJobSitePublish | null\n /**\n * A site job's answers (AGL-3596): the guided start's questions as the person\n * answered them, so a failed start's Try again reopens them filled in. The\n * scalar site inputs only, never anything else a job's inputs carry; absent\n * on every other kind.\n */\n siteInputs?: Record<string, unknown> | null\n /**\n * Whether the machine confirms this job's plan itself (AGL-3594): a guided\n * site start. Its plan parks for the instant the confirmation takes, and a\n * surface reads that park as the build going on (AGL-3596). Absent on every\n * other job.\n */\n autoConfirm?: boolean\n /** A `build` job's items (AGL-3616); absent on every other job. */\n items?: AiJobItemLedger[]\n /**\n * Whether a build's request asked to publish (AGL-3616), so its plan card\n * offers the box that confirms it. Absent on every other job.\n */\n publishAsked?: boolean\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration\n /**\n * A cancel was asked and the job has not ended yet (AGL-3616): the step in\n * flight is finishing or aborting, and nothing after it runs. Absent once\n * the job is terminal, and on every job nobody canceled.\n */\n cancelRequested?: boolean\n /**\n * The empty site this guided start was for was removed (AGL-3936): its\n * page is gone, and Start again opens Create site with the same answers.\n * Absent on every other job.\n */\n siteRemoved?: boolean\n /**\n * An `import` job's waves (AGL-3976), without the page text a wave read\n * holds for the model; absent on every other job.\n */\n siteImport?: AiSiteImportState | null\n}\n"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAiFD,qEAAqE,GACrE,OAAO,MAAMA,eAAqC;IAChD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAqBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-jobs.types.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type { AiEffort } from '../providers/contract'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiBuildPlan } from './ai-build-plan'\nimport type { AiFreeCreditsLeft } from './ai-site-job'\nimport type { AiJobCreditsConfirmation } from './ai-credit-estimate'\nimport type { AiSiteImportState } from './ai-site-import'\nimport type { AiSitePhotos } from './ai-site-photos'\nimport type { AiSiteInspiration } from '../jobs/ai-site-inspiration-signals'\n\n/**\n * AI generation jobs (AGL-2904).\n *\n * A job is one customer brief carried through one or more model steps by a\n * Firestore state machine, so that work longer than a request can survive\n * the request: the console route runs what it can inline, and the platform\n * job beat resumes whatever is left. The document is the whole state — no\n * process holds anything the next beat cannot read back.\n *\n * ── Outputs are drafts, never a publish ──────────────────────────────────\n *\n * Every output a job produces is a NEW draft or a new unpublished version of\n * an existing resource. A job never flips a version pointer, never registers\n * a route and never sends anything; a person opens the draft the job names\n * and publishes it through the door that already exists for that resource.\n * That is what lets a job run with no visitor, no session and no confirm\n * gate — the thing it writes cannot be seen by anyone but the workspace\n * until a member chooses otherwise.\n *\n * ── The lease ────────────────────────────────────────────────────────────\n *\n * A step runs under a lease: an owner id and an `until` instant. Two beats\n * fire a minute apart against a 120 s function timeout, and a console route\n * may still be running the first step when the first beat looks, so the\n * lease is what stops the same step being run — and metered — twice. A\n * claim is a transaction that refuses while another owner's lease is live;\n * an expired lease is simply claimable, which is also how a step abandoned\n * by a frozen process is recovered.\n */\n\n/**\n * What a job produces. `text` and `theme` have runners; every other kind is\n * registered and refuses with \"not available yet\" until its own issue lands,\n * so a kind's presence in the union says the job model accepts it, not that\n * a runner exists.\n */\nexport type AiJobKind =\n | 'page'\n | 'component'\n | 'layout'\n | 'template'\n | 'edit'\n | 'seo'\n | 'site'\n | 'email'\n | 'campaign'\n | 'form'\n | 'experiment'\n | 'insight'\n | 'products'\n | 'crm'\n | 'onboarding'\n | 'workflow'\n | 'logic'\n | 'text'\n | 'theme'\n | 'build'\n /** A change to a site's header or footer menu, made by code (AGL-3728). */\n | 'navigation'\n /** A copy of something a site already has, made by core's duplicate (AGL-3740). */\n | 'duplicate'\n /** A change to a site's settings, made by code and saved live (AGL-3748). */\n | 'settings'\n /** Folders, tags, alt text and names in a site's media library, made by code and saved live (AGL-3752). */\n | 'media'\n /** A change to a site's analytics and advertising tags, made by code and saved live (AGL-3751). */\n | 'tracking'\n /** A person's own website brought in, page by page, in reviewed waves (AGL-3976). */\n | 'import'\n\n/** The union as a value, so a route can validate a body against it. */\nexport const AI_JOB_KINDS: readonly AiJobKind[] = [\n 'page',\n 'component',\n 'layout',\n 'template',\n 'edit',\n 'seo',\n 'site',\n 'email',\n 'campaign',\n 'form',\n 'experiment',\n 'insight',\n 'products',\n 'crm',\n 'onboarding',\n 'workflow',\n 'logic',\n 'text',\n 'theme',\n 'build',\n 'navigation',\n 'duplicate',\n 'settings',\n 'media',\n 'tracking',\n 'import',\n]\n\n/**\n * `queued` — waiting for a runner; `running` — a step holds the lease;\n * `needs_input` — the job stopped for something only the workspace can\n * change (credits, a cap, a switch), and the beat tries it again once that\n * may have changed — or, carrying `input`, for an answer only the person can\n * give (AGL-3978), which the beat leaves alone; `needs_review` — the job stopped for a person's decision\n * (AGL-2935): its plan is ready to confirm, or an answer broke a building\n * rule on its re-ask. No timer can make that decision, so neither the beat\n * nor anything but that person resumes it. The last three are terminal.\n */\nexport type AiJobStatus =\n | 'queued'\n | 'running'\n | 'needs_input'\n | 'needs_review'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport const AI_JOB_TERMINAL_STATUSES: readonly AiJobStatus[] = [\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport type AiJobStepStatus = 'pending' | 'running' | 'done' | 'failed'\n\n/** The step a planned kind runs first (AGL-2935): its plan, before anything is built. */\nexport const AI_JOB_PLAN_STEP = 'plan'\n\nexport interface AiJobStep {\n /** Stable within the job (`plan`, `draft`, `generate`), unique per job. */\n name: string\n status: AiJobStepStatus\n startedAt?: ITimestamp | null\n endedAt?: ITimestamp | null\n /** Credits this step has actually cost, at the plan's credit rate. */\n creditsSpent: number\n /** Customer-safe only; provider detail goes to the server log. */\n error?: string | null\n /**\n * How many times a runner has been handed this step. A retryable provider\n * failure hands the step back as `pending`; the count is what stops a\n * provider outage re-running a step forever.\n */\n attempts?: number\n /**\n * Passes of a step that works through a list (AGL-2910): each one a\n * recorded, metered exchange that asked to continue. Attempts count again\n * from zero after a pass; this count is what bounds the step.\n */\n passes?: number\n /**\n * What the step's model calls cost in tokens and time (AGL-2937), summed\n * over every run that reached the provider. Absent on a step none has.\n */\n tokens?: AiJobStepTokens\n /**\n * The ids the step writes its drafts under, by draft (AGL-3079): minted\n * when the job is created, so every run of the step names the same drafts.\n * Absent on a step that writes none; a step that records none names its\n * drafts by the job's own id.\n */\n draftIds?: Partial<Record<AiJobDraftSlot, string>>\n}\n\n/**\n * A draft a job's kind always writes (AGL-3079): a screen, layout, template,\n * form or component; an email design; a campaign; an automation.\n */\nexport type AiJobDraftSlot =\n | 'screen'\n | 'layout'\n | 'template'\n | 'form'\n | 'component'\n | 'email'\n | 'campaign'\n | 'workflow'\n\n/**\n * A step's measure (AGL-2937): the four token counts the meter prices, the\n * time spent in the runner, and the model and thinking effort of the last\n * run. The machine writes it beside `creditsSpent`, so what a kind of step\n * costs in tokens is read off the job rather than inferred from the month.\n */\nexport interface AiJobStepTokens {\n /** Prompt tokens sent uncached. */\n input: number\n /** Prompt tokens read from the prompt cache. */\n cachedRead: number\n /** Prompt tokens written to the prompt cache. */\n cacheWrite: number\n /** Tokens generated, thinking included. */\n output: number\n /** The model the last run was served by. */\n model: string | null\n /** The thinking effort the last run asked for; `null` when it named none. */\n effort: AiEffort | null\n /** Milliseconds spent in the runner, summed over runs. */\n latencyMs: number\n /** Runs that reached the provider. */\n runs: number\n /**\n * Why the step's latest runs stopped (AGL-3042), oldest first and the last\n * run last: each run's stop reason and the tokens it generated, so a pass\n * cut off at its ceiling reads off the job rather than out of the sums. At\n * most `AI_JOB_STEP_LAST_RUNS` are kept; absent on a step measured before.\n */\n lastRuns?: AiJobStepRunStop[]\n}\n\n/** One run of a step, by why it stopped (AGL-3042). */\nexport interface AiJobStepRunStop {\n /**\n * The stop reason of the run's last model call — `tool_use` or `end_turn`\n * for an answer, `max_tokens` for one cut off at its ceiling, `refusal` —\n * and `null` when the provider named none.\n */\n stopReason: string | null\n /** Tokens the run generated, over every model call it made. */\n output: number\n}\n\nexport type AiJobOutputResource =\n | 'screen'\n | 'reusableComponent'\n | 'layout'\n | 'template'\n | 'form'\n | 'emailScreen'\n | 'campaign'\n | 'product'\n | 'experiment'\n | 'workflow'\n | 'text'\n | 'theme'\n | 'seo'\n | 'insight'\n | 'crm'\n /** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */\n | 'draft'\n | 'logic'\n | 'orgAutomation'\n /** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */\n | 'entry'\n /** A site's settings a build changed (AGL-3748): saved live, on the host document. */\n | 'settings'\n /** Files in a site's media library a build organized (AGL-3752): saved live, on their documents. */\n | 'media'\n /** A site's tracking tags a build changed (AGL-3751): saved live, on the host document. */\n | 'tracking'\n\n/**\n * One thing a job wrote. Addressed by resource and id so the console can\n * build the \"open draft\" link without knowing what the runner did; `text`\n * has no document of its own, so its body rides on the output itself.\n *\n * A `theme` output is a proposal rather than a document (AGL-2938). A site's\n * theme is fields on its host document, and writing those fields is the save\n * the Theme section's editor performs, which a job never does. So the change\n * set rides on the output as `proposal`, and a person puts it in the editor\n * and saves it there, or does not.\n *\n * An `seo` output is a proposal rather than a document (AGL-2910): search\n * listing values for a page or a product, or a site audit's findings and\n * fixes. A screen's listing is served from the screen document itself, so a\n * job never writes it; the values ride on the output as `proposal`, a person\n * puts them in the editor and saves them there, and an audit's content fixes\n * become new versions only when a person applies them.\n *\n * A `crm` output is a proposal too (AGL-2917): a record's summary and next\n * step, an email draft, or an import's column matches. The CRM's own task\n * form, stage route, composer and import drawer are the writes, and a person\n * makes each of them, or does not.\n *\n * A `logic` output is a proposal too (AGL-3603): a site function or variable\n * the Functions & Variables editor opens unsaved. Saving it is the editor's\n * write, which a person makes, or does not.\n *\n * An `orgAutomation` output is a proposal too (AGL-3603): one of the\n * workspace's automations, which the Org automations editor opens unsaved and\n * switched off. `hostId` is `null`: it belongs to no one site.\n */\nexport interface AiJobOutput {\n resource: AiJobOutputResource\n id: string\n /** The unpublished version the job created, where the resource has versions. */\n versionId?: string | null\n /** `null` for an output that belongs to the org rather than one site. */\n hostId: string | null\n /**\n * The site's subdomain, which is what a console URL names a site by: the\n * `[host]` segment resolves by subdomain, so a link built from `hostId`\n * names no site the console can open.\n */\n hostSubdomain?: string | null\n label: string\n /** The body of a `text` output. Absent on every other resource. */\n text?: string\n /**\n * The change set of an output a person applies rather than opens — a\n * `theme` or `seo` proposal — in the shape its job kind's runner defines.\n */\n proposal?: Record<string, unknown>\n /**\n * What the person decides next about this output, in customer-safe words:\n * a routing the draft could not store, or part of the brief it could not\n * build. Absent when there is nothing to decide.\n */\n note?: string | null\n /**\n * What a first visit is estimated to transfer, as the doctrine measured\n * the generated document (AGL-2935) — the weight the proposal shows before\n * anything is applied. Absent where the output is not a page's document.\n */\n load?: AiLoadEstimate | null\n /**\n * For a `draft` output (AGL-3616): the resource whose owner wrote it, as\n * its writer is registered on the resource-draft seam.\n */\n draftResource?: string\n}\n\nexport interface AiJobLease {\n /** The process that holds the step: a route request id or a beat id. */\n owner: string\n until: ITimestamp\n}\n\n/** Why a job stopped for a person (AGL-2935). */\nexport type AiJobReviewReason =\n /** The plan step proposed a plan; generation waits for it to be confirmed. */\n | 'plan'\n /** An answer broke a building rule, and so did the answer to the re-ask. */\n | 'doctrine'\n /**\n * The site is at an allowance its plan sets for what the step writes, such\n * as its shared layouts or its templates (AGL-2909). Freeing one, or a plan\n * that holds more, is the member's decision; trying again runs the step\n * again.\n */\n | 'limit'\n /**\n * A site import's wave is in (AGL-3976): its pages are drafts for the\n * person to review, and the next wave starts only when they say so. Or the\n * import has read the site and waits for the first wave to be started.\n */\n | 'wave'\n /**\n * A site import's next wave needs a current proof that the workspace owns\n * the site (AGL-3976): the proof is older than its 30 days, or was never\n * made. Verifying again and starting the wave runs it.\n */\n | 'ownership'\n\n/**\n * A building rule an answer broke, as the job keeps it: the number, the code,\n * the sentence, and where in the answer it was broken (AGL-3078).\n */\nexport interface AiJobRuleFinding {\n rule: number | null\n code: string\n /** Customer-safe. */\n message: string\n /** The nodes the finding names, by the ids the refused answer gave them. Absent when it names none. */\n nodeIds?: string[]\n /** The plan entries or answer fields it names, as `screens[0].sections[2]`. Absent when it names none. */\n paths?: string[]\n}\n\n/**\n * One node of the parts of a refused answer its findings name (AGL-3078), as\n * the review keeps it: the node's place, its element and the names of what it\n * sets, and never its copy. A Grid keeps the values of its layout props as\n * they were written, so a review can tell a shape the rules refuse from one\n * the model was asked to mend and did not.\n */\nexport interface AiJobReviewOutlineNode {\n /** The node's id, as the refused answer wrote it. */\n id: string\n /** How far below a node a finding names it sits: 0 for that node. */\n depth: number\n componentId: string\n /** The props it sets, by name only. */\n props: string[]\n /** The `sx` keys it sets, by name only; absent when it sets none. */\n sx?: string[]\n /** A Grid's layout props, `container`, `size`, `direction`, `spacing` and the rest, as written. */\n grid?: Record<string, string | number | boolean | null>\n /** Its children's element ids, in order. */\n children: string[]\n}\n\nexport interface AiJobReview {\n reason: AiJobReviewReason\n /** Customer-safe: what the person is asked to decide. */\n message: string\n /** The rules the last answer broke; empty for a plan. */\n findings: AiJobRuleFinding[]\n /**\n * The parts of the refused answer its findings name, each node with the\n * nodes below it (AGL-3078); absent when they name none. Kept with the job\n * for diagnosis, and shown to staff.\n */\n outline?: AiJobReviewOutlineNode[]\n /**\n * The checks' own sentence, where `message` was written for the member\n * instead (AGL-3594): rule numbers and all, for staff. Its presence is what\n * tells the drawer the findings are staff reading too.\n */\n detail?: string\n /**\n * Why trying again cannot work yet (AGL-3594) — the Free allowance left is\n * less than the step can cost — or absent when it can. The button is drawn\n * disabled with this beside it.\n */\n retryRefusal?: string\n /**\n * What a Free workspace had left when a build's plan was kept (AGL-3722),\n * so its plan card says, before Confirm, whether the build fits and offers\n * the go-ahead or a smaller first build when it does not. The confirm door\n * reads what is left again; this is what the card shows.\n */\n freeCredits?: AiFreeCreditsLeft\n}\n\n/**\n * One answer a job waits for (AGL-3978): an id only the person has, which the\n * operation that needs it describes — a tracking change's Meta pixel ID.\n */\nexport interface AiJobInputField {\n /** The plan item the answer fills, by its name in the plan. */\n item: string\n /** The item's argument the answer is written to. */\n arg: string\n /** The field's label, naming the provider: \"Meta pixel ID\". */\n label: string\n /** What an answer looks like, in the words of the card that sets it: \"8–20 digits\". */\n shape: string\n /** The pattern an answer is held to, as a RegExp's source. */\n pattern: string\n}\n\n/** What a job parked `needs_input` for a person asks (AGL-3978). */\nexport interface AiJobInputRequest {\n /** Customer-safe: the question, in the operation's own words. */\n message: string\n fields: AiJobInputField[]\n}\n\nexport type AiJobPlanStatus = 'proposed' | 'confirmed'\n\n/** The plan a job builds from, as the job keeps it once its plan step proposed it. */\nexport interface AiJobPlan extends AiBuildPlan {\n status: AiJobPlanStatus\n /** Names for the inventory ids the plan references, read when it was made. */\n labels: Record<string, string>\n proposedAt: ITimestamp\n confirmedAt: ITimestamp | null\n /** The member who confirmed it. */\n confirmedBy: string | null\n /**\n * A digest of the whole request that produced this plan (AGL-2937): the\n * job's kind, site, brief and scalar inputs, the model that answered, and\n * the prompt as it was rendered — the site inventory included. A later job\n * whose request hashes the same reuses this plan rather than paying for the\n * same answer again. A hash of the request, never of the answer, and no\n * part of the brief is recoverable from it.\n */\n key?: string\n /** The job this plan was reused from, when it was not asked for again. */\n reusedFrom?: string\n /**\n * What a site start read of the sites the person likes (AGL-3975): signals\n * only, each from a closed set or a hex color, which the plan was told the\n * structure of and the look pins the style of. Absent without them.\n */\n inspiration?: AiSiteInspiration\n}\n\n/**\n * Where one item of a `build` job stands (AGL-3616). A build settles item by\n * item: a failed item gives back its own spend and the rest of the plan still\n * runs, so the job keeps one row per item, written only by the machine's\n * `recordStep` — in the same transaction as the spend it accounts for — and\n * by the resume door's retry.\n */\nexport type AiJobItemStatus = 'pending' | 'running' | 'succeeded' | 'degraded' | 'failed' | 'skipped'\n\n/** Why an item failed: a give-back reason when it was ours, else the model's refusal or a review. */\nexport type AiJobItemFailureReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n | 'refused'\n | 'review'\n\nexport interface AiJobItemFailure {\n /** Whether the failure was on our side: only then is the item's spend given back. */\n ours: boolean\n reason: AiJobItemFailureReason\n /** Customer-safe. */\n message: string\n /**\n * The checks' own sentence behind a `doctrine-refused` failure (AGL-3596),\n * rule numbers and all: kept on the job for staff and debugging, never on\n * the wire a customer reads.\n */\n detail?: string\n}\n\nexport interface AiJobItemLedger {\n /** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */\n slot: string\n /** The capability operation that builds it: `page`, `form`, or an owner's op. */\n op: string\n /** What a person reads: the page's title, the creation's or item's name. */\n label: string\n status: AiJobItemStatus\n /** Bumped by Try again; the refund key names it, so each attempt gives back once. */\n attempt: number\n /** What this item's passes cost, every attempt. */\n creditsSpent: number\n /** What this attempt's passes cost so far: what a failure of this attempt gives back. */\n attemptCredits?: number\n /** What was given back for this item's failures. */\n creditsRefunded: number\n /** The key the latest give-back for this item was made under. */\n refundKey?: string\n failure?: AiJobItemFailure | null\n /** The ids of the outputs this item produced. */\n outputs: string[]\n /** The slots whose failure changed how this item was built. */\n degradedBy?: string[]\n /** Customer-safe: what the person should know about this item. */\n note?: string | null\n /**\n * When this attempt settled, as an ISO string (AGL-3596): the moment the\n * next item started, which is what its progress row counts from. Absent\n * while the item is open, and on a ledger written before it was kept.\n */\n settledAt?: string | null\n}\n\n/** Whether a build's own planning was charged or given back (AGL-3616). */\nexport interface AiJobOrchestration {\n /** What the plan step spent. */\n creditsSpent: number\n /** `charged` once anything was delivered; `refunded` when nothing was. */\n settled: 'pending' | 'charged' | 'refunded'\n}\n\nexport interface AiJob {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n status: AiJobStatus\n /** The customer's brief, verbatim. Expires with the document. */\n brief: string\n /** Kind-specific inputs the runner reads (a screen id to edit, a tone). */\n inputs: Record<string, unknown>\n /**\n * The model the creator picked for the job's steps (AGL-2942), or `null`\n * for Auto. Each step honors it only where the plan and the allotment\n * allowlists allow it, and runs on the routing table otherwise.\n */\n model?: string | null\n steps: AiJobStep[]\n outputs: AiJobOutput[]\n /**\n * The nominal credit figure held against the job while steps are\n * outstanding. The meter's real bound is the per-step message reservation\n * and the org's monthly ceiling; this is the number the console shows so\n * a running job is not read as free.\n */\n creditsReserved: number\n creditsSpent: number\n /**\n * Credits the job gave back because it failed on our side (AGL-3594),\n * summed over its give-backs; absent when it gave none. `creditsSpent`\n * stays what ran, so what the job cost is the one less the other.\n */\n refundedCredits?: number\n /** Why the latest give-back was made (`AiJobRefundReason`). */\n refundReason?: string\n /** How many give-backs the job has had: the next one's key. */\n refunds?: number\n createdBy: string\n createdAt: ITimestamp\n updatedAt: ITimestamp\n /** TTL — the brief is verbatim customer text and expires like an exchange. */\n expiresAt: ITimestamp\n /** Customer-safe only. */\n error?: string | null\n lease?: AiJobLease | null\n /** The plan the job builds from, once its plan step proposed one. */\n plan?: AiJobPlan | null\n /** What the person is asked to decide while the job is `needs_review`. */\n review?: AiJobReview | null\n /**\n * What the job waits for while it is `needs_input` for a person rather than\n * the meter (AGL-3978): the fields only they can fill, an id the planner\n * must never make up. Its plan and the plan's review are kept beside it,\n * and the answer returns the job to that review.\n */\n input?: AiJobInputRequest | null\n /**\n * What a person applied from the job's outputs (AGL-2910). Written by the\n * door that applied them, never by a step runner, and absent until then.\n */\n applied?: AiJobApplied | null\n /**\n * What a guided site start put live when it finished (AGL-3596): written\n * once by the site step's last pass, never by a person. Absent on every\n * other job, which leaves its pages as drafts.\n */\n sitePublish?: AiJobSitePublish | null\n /** A `build` job's items, one row each (AGL-3616); absent on every other kind. */\n items?: AiJobItemLedger[] | null\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration | null\n /**\n * A Free job started past what was left (AGL-3722): the person chose to\n * build what fits, knowing it pauses where the credits run out. Who, when,\n * what was left and the range they were shown; absent on every job that fit.\n */\n creditsConfirmed?: AiJobCreditsConfirmation | null\n /**\n * A person asked to stop the job (AGL-3616): written only by the cancel\n * door, in the transaction that decides it. A job no step holds ends\n * `canceled` in that same write; a job whose step holds a live lease keeps\n * running until the step finishes or aborts, and the machine ends it\n * `canceled` at its next write. Absent on a job nobody canceled.\n */\n cancelRequested?: AiJobCancelRequest | null\n /**\n * The empty site this guided start was for was removed (AGL-3936): the\n * job was cancelled or failed before it built anything, nobody chose what\n * to do next within the grace period, and the AI jobs beat deleted the\n * site. Written once, by the beat, after the site is gone. Absent on every\n * other job.\n */\n siteRemoved?: AiJobSiteRemoved | null\n /**\n * An `import` job's site, read and cut into waves (AGL-3976): what was\n * found, what is brought in in which wave, what was left out and why, and\n * each wave's state. Written only by the machine's `recordStep`, from what\n * the import step returns; absent on every other kind.\n */\n siteImport?: AiSiteImportState | null\n /**\n * A `site` job's pictures left on a starter photo (AGL-3988): each page\n * picture and dataset record its library could not fill in its own pass,\n * with the picks the image hosts refused. Written only by the machine's\n * `recordStep`, from what the site step returns; emptied by its late photo\n * pass before the site is published. Never on the wire. Absent on every\n * other kind.\n */\n sitePhotos?: AiSitePhotos | null\n}\n\n/** When the AI jobs beat removed a guided start's empty site, and which (AGL-3936). */\nexport interface AiJobSiteRemoved {\n at: ITimestamp\n /** The site's name as it was, for a sentence that names it. */\n name: string | null\n}\n\n/** Who asked to cancel a job, and when (AGL-3616). */\nexport interface AiJobCancelRequest {\n at: ITimestamp\n /** The member or staff account that asked; `null` when nobody was named. */\n by: string | null\n}\n\n/**\n * A guided site start's publish (AGL-3596): the pages that went live, with\n * the address each answers, and the pages that stayed drafts, each with the\n * plain sentence for why. A page that stayed a draft never fails the job.\n */\nexport interface AiJobSitePublish {\n /** The site's live address, `https://<subdomain>.aglyn.app/`; `null` when the site has no subdomain. */\n liveUrl: string | null\n published: Array<{ id: string; label: string; path: string }>\n drafts: Array<{ id: string; label: string; reason: string }>\n}\n\n/**\n * The record of an apply (AGL-2910): what became a new unpublished version,\n * and what waits in its editor for a person to save.\n */\nexport interface AiJobApplied {\n at: ITimestamp\n /** The member who applied. */\n by: string\n /** The new version opened for each resource, by resource id. */\n versions: Record<string, string>\n /** Resource ids whose proposed values wait in their editor, unsaved. */\n staged: string[]\n}\n\n/** The plan on the wire: every instant an ISO string. */\nexport interface AiJobPlanSummary extends AiBuildPlan {\n status: AiJobPlanStatus\n labels: Record<string, string>\n proposedAt: string | null\n confirmedAt: string | null\n confirmedBy: string | null\n key?: string\n reusedFrom?: string\n}\n\n/**\n * The wire form the console reads: every instant an ISO string, the lease\n * reduced to whether a runner holds the job. The brief and the outputs\n * travel as they are — the reader is a member of the org that wrote them.\n */\nexport interface AiJobSummary {\n id: string\n orgId: string\n hostId: string | null\n kind: AiJobKind\n status: AiJobStatus\n brief: string\n /**\n * The batch a door created this job as one of (AGL-2911), read off the\n * job's inputs; `null` for a job created on its own. The console groups a\n * run of jobs by it, which is the one thing it cannot do from a list whose\n * rows carry no inputs — and the only input the wire form carries, because\n * a batch id names nothing the member wrote.\n */\n batch: string | null\n steps: Array<{\n name: string\n status: AiJobStepStatus\n startedAt: string | null\n endedAt: string | null\n creditsSpent: number\n error: string | null\n }>\n outputs: AiJobOutput[]\n creditsReserved: number\n creditsSpent: number\n /** Credits given back for a failure on our side (AGL-3594); 0 when none. */\n refundedCredits?: number\n createdBy: string\n createdAt: string\n updatedAt: string\n error: string | null\n running: boolean\n plan: AiJobPlanSummary | null\n review: AiJobReview | null\n /** The fields a `needs_input` job waits for a person to fill (AGL-3978); absent otherwise. */\n input?: AiJobInputRequest | null\n /** What a person applied from the outputs (AGL-2910); instants as ISO strings. */\n applied?: {\n at: string | null\n by: string\n versions: Record<string, string>\n staged: string[]\n } | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: AiJobSitePublish | null\n /**\n * A site job's answers (AGL-3596): the guided start's questions as the person\n * answered them, so a failed start's Try again reopens them filled in. The\n * scalar site inputs only, never anything else a job's inputs carry; absent\n * on every other kind.\n */\n siteInputs?: Record<string, unknown> | null\n /**\n * Whether the machine confirms this job's plan itself (AGL-3594): a guided\n * site start. Its plan parks for the instant the confirmation takes, and a\n * surface reads that park as the build going on (AGL-3596). Absent on every\n * other job.\n */\n autoConfirm?: boolean\n /** A `build` job's items (AGL-3616); absent on every other job. */\n items?: AiJobItemLedger[]\n /**\n * Whether a build's request asked to publish (AGL-3616), so its plan card\n * offers the box that confirms it. Absent on every other job.\n */\n publishAsked?: boolean\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration\n /**\n * A cancel was asked and the job has not ended yet (AGL-3616): the step in\n * flight is finishing or aborting, and nothing after it runs. Absent once\n * the job is terminal, and on every job nobody canceled.\n */\n cancelRequested?: boolean\n /**\n * The empty site this guided start was for was removed (AGL-3936): its\n * page is gone, and Start again opens Create site with the same answers.\n * Absent on every other job.\n */\n siteRemoved?: boolean\n /**\n * An `import` job's waves (AGL-3976), without the page text a wave read\n * holds for the model; absent on every other job.\n */\n siteImport?: AiSiteImportState | null\n}\n"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAkFD,qEAAqE,GACrE,OAAO,MAAMA,eAAqC;IAChD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAqBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { StockPhotoSearchRequest } from '@aglyn/aglyn/plugin-manager/stock-photo-provider';
18
+ import type { AiLayoutPicturePhoto, AiLayoutPictureRole } from '../layout-language/ai-layout-pictures';
19
+ /**
20
+ * THE PICTURES A SITE START LEFT ON A STARTER PHOTO (AGL-3988).
21
+ *
22
+ * A guided start's pages and its datasets' records are each built in a pass
23
+ * of their own, minutes apart. A picture the stock library could not fill in
24
+ * its pass took a starter photo there: its picks' downloads refused by the
25
+ * library's image hosts in a burst, or no photo of the job's own to take
26
+ * yet. The v1.0.0-beta.271 Laurel Fork Photography start (2026-10-19 23:05Z)
27
+ * filed all twelve "Gallery collections" covers and its home hero under an
28
+ * office team and a desk: pixabay.com's Cloudflare answered 429 to 22
29
+ * downloads while the covers were built, before the job had copied any
30
+ * photo, and to 11 more on the home page, the first page built.
31
+ *
32
+ * Each such picture is written down here, with the picks the hosts refused,
33
+ * on the job, by the machine, in the write that records its pass. The site
34
+ * step's late photo pass (`jobs/ai-site-late-photos.ts`) reads it once every
35
+ * page is built and before the site is published: it asks the refused picks
36
+ * for again, a minute after the refusal, and gives every picture still on a
37
+ * starter one of the photos the job copied. Pure.
38
+ */
39
+ /** Where a starter photo is served from: the platform's own files, on every site's origin. */
40
+ export declare const AI_SITE_STARTER_PREFIX = "/_static/starter/";
41
+ /** Whether an address is a starter photo's. */
42
+ export declare function aiSiteIsStarterSrc(src: unknown): boolean;
43
+ /** The picks a slot kept in hand whose downloads the library's image hosts refused, to ask for again. */
44
+ export interface AiStockRefusedPicks {
45
+ /** The search they came from, as it was sent: asked again, the provider's cache answers it. */
46
+ request: StockPhotoSearchRequest;
47
+ /** The picks' source keys (`pixabay:5401602`), best first. */
48
+ keys: string[];
49
+ /** When the last of them was refused, ISO. */
50
+ at: string;
51
+ }
52
+ /** A picture of one of the start's pages that holds a starter photo. */
53
+ export interface AiSiteStarterSlot {
54
+ /** The `image` node. */
55
+ image: string;
56
+ /** What the picture was to show, in the slot's own words: a starter's alt text is the starter's. */
57
+ words: string;
58
+ role: AiLayoutPictureRole;
59
+ /** The frame's shape, width over height. */
60
+ aspect: number;
61
+ refused?: AiStockRefusedPicks;
62
+ }
63
+ /** A page of the start with the pictures it left on a starter. */
64
+ export interface AiSiteStarterPage {
65
+ /** The page's draft. */
66
+ screen: string;
67
+ slots: AiSiteStarterSlot[];
68
+ }
69
+ /** A record of a pictured list, as its photo was written. */
70
+ export interface AiSiteListRecord {
71
+ /** The record's name: its first field's value. */
72
+ name: string;
73
+ /** What the list files it under, where it files its records (AGL-3856). */
74
+ category?: string;
75
+ /** The value its photo field holds: a starter's path, or the library photo's CDN path. */
76
+ value: string;
77
+ /** The library photo it shows, with its size and words; absent on a starter. */
78
+ photo?: AiLayoutPicturePhoto;
79
+ refused?: AiStockRefusedPicks;
80
+ }
81
+ /** A pictured list of the start, one of whose records holds a starter photo. */
82
+ export interface AiSiteStarterList {
83
+ /** The dataset. */
84
+ dataset: string;
85
+ /** Its photo field, by id. */
86
+ imageField: string;
87
+ /** The field its records keep what their photo shows in, by id (AGL-3856). */
88
+ altField?: string;
89
+ /** A list of the business's people: a record of it never takes a photo of anything but a person. */
90
+ people?: boolean;
91
+ /** Every record, in order, so a record's neighbors are known. */
92
+ records: AiSiteListRecord[];
93
+ }
94
+ /** What a site job holds on its pictures that took a starter, until its late photo pass. */
95
+ export interface AiSitePhotos {
96
+ pages: AiSiteStarterPage[];
97
+ lists: AiSiteStarterList[];
98
+ /**
99
+ * The late photo passes that ran out of time and handed this back (AGL-3988).
100
+ * The site step publishes on the last pass its bound gives them, whatever
101
+ * the pictures hold then, so a burst that never clears never holds a site
102
+ * back.
103
+ */
104
+ latePasses?: number;
105
+ }
106
+ /**
107
+ * The job's photos state once a unit reported its own (AGL-3988): a page's
108
+ * or a list's entry replaces the one it had, by its draft or its dataset;
109
+ * every other entry stays. `null` where neither holds anything.
110
+ */
111
+ export declare function aiSiteMergePhotos(previous: AiSitePhotos | null | undefined, next: Partial<AiSitePhotos> | null | undefined): AiSitePhotos | null;
112
+ /**
113
+ * Whether a site job owes its late photo pass: a page picture or a list
114
+ * record it left on a starter. With `pageIds`, only a picture of one of
115
+ * those pages counts: a page that was not built is never published.
116
+ */
117
+ export declare function aiSitePhotosOwed(photos: AiSitePhotos | null | undefined, pageIds?: readonly string[]): boolean;
118
+ /** When the newest refusal the state holds was made, in ms; `null` where none was. */
119
+ export declare function aiSitePhotosLastRefusalMs(photos: AiSitePhotos | null | undefined): number | null;