@aglyn/plugins-ai 1.0.0-beta.234 → 1.0.0-beta.236

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 (137) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +25 -0
  3. package/src/lib/activity/ai-activity-actions.js +85 -4
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-brief-dialog.component.js +46 -2
  6. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  7. package/src/lib/components/ai-campaign-create.component.js +1 -1
  8. package/src/lib/components/ai-campaign-create.component.js.map +1 -1
  9. package/src/lib/components/ai-credits-card.component.js +3 -3
  10. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  11. package/src/lib/components/ai-crm-email-draft.component.js +1 -1
  12. package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
  13. package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
  14. package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
  15. package/src/lib/components/ai-crm-record-card.component.js +1 -1
  16. package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-automation.component.js +4 -2
  18. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  19. package/src/lib/components/ai-describe-org-automation.component.js +1 -1
  20. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
  21. package/src/lib/components/ai-experiment-cards.component.js +2 -2
  22. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  23. package/src/lib/components/ai-explain-automation.component.js +1 -1
  24. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  25. package/src/lib/components/ai-insight-card.component.js +3 -1
  26. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  27. package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
  28. package/src/lib/components/ai-jobs-list-page.component.js +15 -2
  29. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  30. package/src/lib/components/ai-logic.component.js +5 -3
  31. package/src/lib/components/ai-logic.component.js.map +1 -1
  32. package/src/lib/components/ai-media-create.component.js +88 -16
  33. package/src/lib/components/ai-media-create.component.js.map +1 -1
  34. package/src/lib/components/ai-model-selector.component.js +8 -0
  35. package/src/lib/components/ai-model-selector.component.js.map +1 -1
  36. package/src/lib/components/ai-overlay-cards.component.js +2 -2
  37. package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
  38. package/src/lib/components/ai-product-copy-card.component.js +2 -1
  39. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  40. package/src/lib/components/ai-products-hub-card.component.js +2 -1
  41. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  42. package/src/lib/components/ai-revise-automation.component.js +1 -1
  43. package/src/lib/components/ai-revise-automation.component.js.map +1 -1
  44. package/src/lib/components/ai-seo-fields-card.component.js +7 -3
  45. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  46. package/src/lib/components/ai-site-batch-card.component.js +7 -4
  47. package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
  48. package/src/lib/components/ai-site-build-page.component.js +7 -3
  49. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  50. package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
  51. package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
  52. package/src/lib/components/ai-site-start-card.component.js +62 -29
  53. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  54. package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
  55. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  56. package/src/lib/components/ai-upsell-dialog.component.js +17 -3
  57. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  58. package/src/lib/components/ai-usage-strip.component.js +13 -2
  59. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +20 -3
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +7 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/billing-ai-allotments.component.js +2 -2
  65. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  66. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  67. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  68. package/src/lib/components/billing-assist-overage-card.component.js +2 -2
  69. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  70. package/src/lib/components/host-ai-allotments.component.js +2 -2
  71. package/src/lib/components/host-ai-allotments.component.js.map +1 -1
  72. package/src/lib/components/member-ai-allotment-card.component.js +2 -2
  73. package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
  74. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  75. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  76. package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
  77. package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
  78. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
  79. package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
  80. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  81. package/src/lib/jobs/ai-job-layout-language.js +4 -1
  82. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  83. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +12 -0
  84. package/src/lib/jobs/ai-job-layout-site-pages.js +20 -0
  85. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  86. package/src/lib/jobs/ai-job-page-language.d.ts +10 -1
  87. package/src/lib/jobs/ai-job-page-language.js +41 -8
  88. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  89. package/src/lib/jobs/ai-job-page-step.js +14 -3
  90. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  91. package/src/lib/jobs/ai-job-site-content.d.ts +75 -7
  92. package/src/lib/jobs/ai-job-site-content.js +205 -32
  93. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  94. package/src/lib/jobs/ai-job-site-step.d.ts +8 -0
  95. package/src/lib/jobs/ai-job-site-step.js +61 -12
  96. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  97. package/src/lib/layout-language/ai-layout-compiler.d.ts +8 -0
  98. package/src/lib/layout-language/ai-layout-compiler.js +308 -10
  99. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  100. package/src/lib/layout-language/ai-layout-design.d.ts +7 -0
  101. package/src/lib/layout-language/ai-layout-design.js +23 -11
  102. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  103. package/src/lib/layout-language/ai-layout-frame.d.ts +6 -0
  104. package/src/lib/layout-language/ai-layout-frame.js +12 -3
  105. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  106. package/src/lib/layout-language/ai-layout-gaps.js +18 -2
  107. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  108. package/src/lib/layout-language/ai-layout-links.d.ts +26 -1
  109. package/src/lib/layout-language/ai-layout-links.js +24 -4
  110. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  111. package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
  112. package/src/lib/layout-language/ai-layout-listings.js +187 -0
  113. package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
  114. package/src/lib/model/ai-site-build-progress.js +27 -12
  115. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  116. package/src/lib/model/ai-site-job.d.ts +33 -8
  117. package/src/lib/model/ai-site-job.js +38 -18
  118. package/src/lib/model/ai-site-job.js.map +1 -1
  119. package/src/lib/model/ai-site-kinds.d.ts +30 -8
  120. package/src/lib/model/ai-site-kinds.js +731 -117
  121. package/src/lib/model/ai-site-kinds.js.map +1 -1
  122. package/src/lib/model/ai-site-start-seo.d.ts +30 -0
  123. package/src/lib/model/ai-site-start-seo.js +132 -11
  124. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  125. package/src/lib/model/ai-site-start.d.ts +2 -2
  126. package/src/lib/model/ai-site-start.js +3 -3
  127. package/src/lib/model/ai-site-start.js.map +1 -1
  128. package/src/lib/plugin.js +15 -3
  129. package/src/lib/plugin.js.map +1 -1
  130. package/src/lib/runtime/ai-palette.generated.js +13 -3
  131. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  132. package/src/lib/server/ai-admin-conversations.d.ts +9 -0
  133. package/src/lib/server/ai-admin-conversations.js +151 -9
  134. package/src/lib/server/ai-admin-conversations.js.map +1 -1
  135. package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
  136. package/src/lib/usage/staff-org-ai-conversations.js +45 -0
  137. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-language.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 { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiLayoutDesignChoices, type AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiCompileLayoutFrame } from '../layout-language/ai-layout-frame'\nimport {\n AI_LAYOUT_FRAME_TOOL,\n AI_LAYOUT_LANGUAGE_TEXT,\n aiReadLayoutFrame,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiGenerationCheck, AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiSiteKindDesignLines } from '../model/ai-site-kinds'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine } from './ai-job-generation'\nimport {\n AI_LAYOUT_SITE_PAGES_MAX,\n aiLayoutIsHomeSlug,\n aiLayoutSitePages,\n} from './ai-job-layout-site-pages'\nimport {\n AI_LAYOUT_FRAME_THINKING_TOKENS,\n aiLayoutPageTargets,\n} from './ai-job-page-language'\nimport { AI_SITE_NAME_TOKEN } from './ai-layout-site-facts'\n\n/**\n * A site's layout built in the compact layout language (AGL-3660): the layout\n * step's pass for a guided start's and a build's layout unit. The model\n * designs the header and the footer — their bands, the call to action, the\n * footer's columns and every word — in one short answer through\n * `submit_frame`, and the compiler writes the layout around what the site\n * owes it: its name as the brand, its pages as the navigation and the phone\n * menu, the Layout Slot, and the footer at the bottom of a short page\n * (`ai-layout-frame.ts`).\n */\n\n/**\n * The most a frame's answer asks for: two sections of a few blocks each (the\n * live runs of 2026-10-07 wrote 442 to 583), and the thinking before them.\n */\nexport const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1_200 + AI_LAYOUT_FRAME_THINKING_TOKENS\n\n/** A language layout pass's time, on the tier the layout step is served from. */\nexport const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS,\n})\n\n/** The generation kind a frame is asked under, which the doctrine scopes. */\nexport const AI_LAYOUT_FRAME_KIND = 'layout-frame'\n\n/** The language, as the frame door's cached instructions. */\nexport const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n \"Now you design a site's header and footer, each one section of the language, answered with submit_frame.\",\n \"- header: the platform writes the site's name and its navigation itself. Choose its band and align (start, or center for a centered navigation), and give it at most one button: the call to action every page shows.\",\n '- footer: a section like any other, usually a row of two or three columns: a few words about the business, the details the brief gives, and a list of links (items with to). Put no contact detail, opening hours or address in it that the brief does not give, and no gap in square brackets for one.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The site's name as the brand: the name the job was given, else the token the site's own name fills. */\nexport function aiLayoutSiteName(job: Pick<AiJob, 'inputs'>): string {\n const given = job.inputs?.['businessName']\n return typeof given === 'string' && given.trim()\n ? given.trim()\n : AI_SITE_NAME_TOKEN\n}\n\n/** The pages a frame's navigation links: the planned pages, else the site's own, in order. */\nexport function aiLayoutNavPages(\n job: Pick<AiJob, 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutPage[] {\n // The header and the footer link the pages the job was told (AGL-3660),\n // else the site's own, the home page always and first, labelled Home. The\n // model draws neither list, so it cannot drop a page.\n const planned = aiLayoutSitePages(job.inputs)\n const own = (inventory?.screens ?? [])\n .filter((row) => !row.template)\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug }))\n const siteHome = planned.some((page) => aiLayoutIsHomeSlug(page.slug))\n ? []\n : own.filter((page) => aiLayoutIsHomeSlug(page.slug)).slice(0, 1)\n const pages = [...siteHome, ...(planned.length ? planned : own)].filter((page) => page.label.trim())\n const home = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return [\n ...(home ? [{ ...home, label: 'Home' }] : []),\n ...pages.filter((page) => page !== home && !aiLayoutIsHomeSlug(page.slug)),\n ].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The home page the brand links: the planned home, else the site's. */\nexport function aiLayoutHomeId(\n pages: readonly AiLayoutPage[],\n inventory: AiSiteInventory | null,\n): string | null {\n const planned = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return planned?.id ?? aiHomeScreenIds(inventory)[0] ?? null\n}\n\n/** The frame's user turn: the site, its brief, its pages and its form. */\nexport function aiLayoutFramePrompt(input: {\n job: Pick<AiJob, 'brief' | '$id'> & Partial<Pick<AiJob, 'inputs'>>\n siteName: string\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n}): string {\n const { job, pages, targets } = input\n return [\n `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `\"${input.siteName}\"`}`,\n aiJobBriefLine(job),\n ...aiSiteKindDesignLines(job.inputs),\n pages.length\n ? `Its pages, which the navigation links: ${pages.map((page) => `${page.label} (page:${page.id})`).join(', ')}.`\n : 'It has no pages yet.',\n targets.forms.length || targets.formPageId\n ? 'A call to action that asks a visitor to get in touch goes to form.'\n : 'The site has no form yet.',\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A validated language layout. */\nexport interface AiLayoutFrameBuilt {\n rootId: string\n nodes: NodesMap\n load: AiLoadEstimate | null\n /** What the last answer's gaps took out of the frame (`ai-layout-gaps.ts`). */\n dropped: string[]\n}\n\n/** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */\nexport function aiLayoutFrameCheck(input: {\n /** The header arrangement the site's look chose (AGL-3660); the answer's otherwise. */\n headerAlign?: 'start' | 'center'\n siteName: string\n homeId: string | null\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n extend: (tree: AiValidatedTree) => AiDoctrineViolation[]\n /** The site's design (`aiLayoutDesignOf`), whose pages' closing band the footer sits under. */\n design?: AiLayoutDesign | null\n}): AiGenerationCheck<AiLayoutFrameBuilt> {\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const frame = aiReadLayoutFrame(answer)\n if (input.headerAlign && frame.header) frame.header.align = input.headerAlign\n const compiled = aiCompileLayoutFrame(\n frame,\n {\n siteName: input.siteName,\n homeId: input.homeId,\n navPages: input.pages,\n closesDark: input.design ? aiLayoutDesignChoices(input.design).coverClose : false,\n },\n input.targets,\n )\n const context = {\n screenIds: [\n ...new Set([\n ...input.targets.pages.map((page) => page.id),\n ...input.pages.map((page) => page.id),\n ...(input.homeId ? [input.homeId] : []),\n ]),\n ],\n formIds: input.targets.forms.map((form) => form.id),\n homeScreenIds: input.targets.homeIds,\n codeBuilt: true,\n }\n const stored = aiLayoutStoredTree(compiled.tree, 'layout', context)\n if (stored.ok === false) {\n console.error('ai layout frame: a compiled layout did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This layout could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a frame every published page shows.\n const navPages = [...input.targets.pages, ...input.pages]\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n stored.rootId,\n {\n pages: navPages,\n ids: [...navPages.map((page) => page.id), ...input.targets.forms.map((form) => form.id)],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: stored.rootId, nodes },\n 'layout',\n context,\n )\n const violations = [\n ...report.violations,\n ...copy.violations,\n ...input.extend({\n rootId: stored.rootId,\n nodes,\n repairs: [],\n sourceIds: {},\n score: report.score as AiValidatedTree['score'],\n load: report.load,\n }),\n ]\n return {\n value: violations.length\n ? null\n : { rootId: stored.rootId, nodes, load: report.load, dropped },\n violations,\n }\n }\n}\n\n/** What a frame's links resolve against: the site's pages and its form, as a page's do. */\nexport function aiLayoutFrameTargets(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutTargets {\n return aiLayoutPageTargets({ job, inventory, own: [] })\n}\n"],"names":["aiLayoutDesignChoices","aiCompileLayoutFrame","AI_LAYOUT_LANGUAGE_TEXT","aiReadLayoutFrame","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","aiHomeScreenIds","AI_STEP_TIERS","validateAiDoctrineTree","aiSiteKindDesignLines","aiJobStepBudget","aiJobBriefLine","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","aiLayoutSitePages","AI_LAYOUT_FRAME_THINKING_TOKENS","aiLayoutPageTargets","AI_SITE_NAME_TOKEN","AI_JOB_LAYOUT_LANGUAGE_TOKENS","AI_JOB_LAYOUT_LANGUAGE_BUDGET","tier","maxTokens","AI_LAYOUT_FRAME_KIND","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutSiteName","job","given","inputs","trim","aiLayoutNavPages","inventory","planned","own","screens","filter","row","template","map","id","label","name","slug","siteHome","some","page","slice","pages","length","home","find","aiLayoutHomeId","aiLayoutFramePrompt","input","targets","siteName","forms","formPageId","$id","aiLayoutFrameCheck","answers","answer","last","frame","headerAlign","header","align","compiled","homeId","navPages","closesDark","design","coverClose","context","screenIds","Set","formIds","form","homeScreenIds","homeIds","codeBuilt","stored","tree","ok","console","error","value","violations","rule","code","message","detail","copy","nodes","rootId","ids","dropped","report","extend","repairs","sourceIds","score","load","aiLayoutFrameTargets"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-language.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 { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { aiLayoutDesignChoices, type AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiCompileLayoutFrame } from '../layout-language/ai-layout-frame'\nimport {\n AI_LAYOUT_FRAME_TOOL,\n AI_LAYOUT_LANGUAGE_TEXT,\n aiReadLayoutFrame,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport type { AiGenerationCheck, AiValidatedTree } from '../runtime/ai-doctrine'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiSiteKindDesignLines } from '../model/ai-site-kinds'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine } from './ai-job-generation'\nimport {\n AI_LAYOUT_SITE_PAGES_MAX,\n aiLayoutIsHomeSlug,\n aiLayoutSitePages,\n} from './ai-job-layout-site-pages'\nimport {\n AI_LAYOUT_FRAME_THINKING_TOKENS,\n aiLayoutPageTargets,\n} from './ai-job-page-language'\nimport { AI_SITE_NAME_TOKEN } from './ai-layout-site-facts'\n\n/**\n * A site's layout built in the compact layout language (AGL-3660): the layout\n * step's pass for a guided start's and a build's layout unit. The model\n * designs the header and the footer — their bands, the call to action, the\n * footer's columns and every word — in one short answer through\n * `submit_frame`, and the compiler writes the layout around what the site\n * owes it: its name as the brand, its pages as the navigation and the phone\n * menu, the Layout Slot, and the footer at the bottom of a short page\n * (`ai-layout-frame.ts`).\n */\n\n/**\n * The most a frame's answer asks for: two sections of a few blocks each (the\n * live runs of 2026-10-07 wrote 442 to 583), and the thinking before them.\n */\nexport const AI_JOB_LAYOUT_LANGUAGE_TOKENS = 1_200 + AI_LAYOUT_FRAME_THINKING_TOKENS\n\n/** A language layout pass's time, on the tier the layout step is served from. */\nexport const AI_JOB_LAYOUT_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_TOKENS,\n})\n\n/** The generation kind a frame is asked under, which the doctrine scopes. */\nexport const AI_LAYOUT_FRAME_KIND = 'layout-frame'\n\n/** The language, as the frame door's cached instructions. */\nexport const AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n \"Now you design a site's header and footer, each one section of the language, answered with submit_frame.\",\n \"- header: the platform writes the site's name and its navigation itself. Choose its band and align (start, or center for a centered navigation), and give it at most one button: the call to action every page shows.\",\n '- footer: a section like any other, usually a row of two or three columns: a few words about the business, the details the brief gives, and a list of links (items with to). Put no contact detail, opening hours or address in it that the brief does not give, and no gap in square brackets for one.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The site's name as the brand: the name the job was given, else the token the site's own name fills. */\nexport function aiLayoutSiteName(job: Pick<AiJob, 'inputs'>): string {\n const given = job.inputs?.['businessName']\n return typeof given === 'string' && given.trim()\n ? given.trim()\n : AI_SITE_NAME_TOKEN\n}\n\n/** The pages a frame's navigation links: the planned pages, else the site's own, in order. */\nexport function aiLayoutNavPages(\n job: Pick<AiJob, 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutPage[] {\n // The header and the footer link the pages the job was told (AGL-3660),\n // else the site's own, the home page always and first, labelled Home. The\n // model draws neither list, so it cannot drop a page.\n const planned = aiLayoutSitePages(job.inputs)\n const own = (inventory?.screens ?? [])\n .filter((row) => !row.template)\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug }))\n const siteHome = planned.some((page) => aiLayoutIsHomeSlug(page.slug))\n ? []\n : own.filter((page) => aiLayoutIsHomeSlug(page.slug)).slice(0, 1)\n const pages = [...siteHome, ...(planned.length ? planned : own)].filter((page) => page.label.trim())\n const home = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return [\n ...(home ? [{ ...home, label: 'Home' }] : []),\n ...pages.filter((page) => page !== home && !aiLayoutIsHomeSlug(page.slug)),\n ].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The home page the brand links: the planned home, else the site's. */\nexport function aiLayoutHomeId(\n pages: readonly AiLayoutPage[],\n inventory: AiSiteInventory | null,\n): string | null {\n const planned = pages.find((page) => aiLayoutIsHomeSlug(page.slug))\n return planned?.id ?? aiHomeScreenIds(inventory)[0] ?? null\n}\n\n/** The frame's user turn: the site, its brief, its pages and its form. */\nexport function aiLayoutFramePrompt(input: {\n job: Pick<AiJob, 'brief' | '$id'> & Partial<Pick<AiJob, 'inputs'>>\n siteName: string\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n}): string {\n const { job, pages, targets } = input\n return [\n `Site: ${input.siteName === AI_SITE_NAME_TOKEN ? 'the business the brief describes' : `\"${input.siteName}\"`}`,\n aiJobBriefLine(job),\n ...aiSiteKindDesignLines(job.inputs),\n pages.length\n ? `Its pages, which the navigation links: ${pages.map((page) => `${page.label} (page:${page.id})`).join(', ')}.`\n : 'It has no pages yet.',\n targets.forms.length || targets.formPageId\n ? 'A call to action that asks a visitor to get in touch goes to form.'\n : 'The site has no form yet.',\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/** A validated language layout. */\nexport interface AiLayoutFrameBuilt {\n rootId: string\n nodes: NodesMap\n load: AiLoadEstimate | null\n /** What the last answer's gaps took out of the frame (`ai-layout-gaps.ts`). */\n dropped: string[]\n}\n\n/** The check a frame answer is held to: read, compiled, stored and checked as a layout, with the layout door's own checks. */\nexport function aiLayoutFrameCheck(input: {\n /** The header arrangement the site's look chose (AGL-3660); the answer's otherwise. */\n headerAlign?: 'start' | 'center'\n siteName: string\n homeId: string | null\n pages: readonly AiLayoutPage[]\n targets: AiLayoutTargets\n extend: (tree: AiValidatedTree) => AiDoctrineViolation[]\n /** The site's design (`aiLayoutDesignOf`), whose pages' closing band the footer sits under. */\n design?: AiLayoutDesign | null\n}): AiGenerationCheck<AiLayoutFrameBuilt> {\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const frame = aiReadLayoutFrame(answer)\n if (input.headerAlign && frame.header) frame.header.align = input.headerAlign\n const compiled = aiCompileLayoutFrame(\n frame,\n {\n siteName: input.siteName,\n homeId: input.homeId,\n navPages: input.pages,\n closesDark: input.design ? aiLayoutDesignChoices(input.design).coverClose : false,\n // A site that sells carries its cart in the header (AGL-3676).\n cart: (input.targets.listings ?? []).some((listing) => listing.kind === 'products'),\n },\n input.targets,\n )\n const context = {\n screenIds: [\n ...new Set([\n ...input.targets.pages.map((page) => page.id),\n ...input.pages.map((page) => page.id),\n ...(input.homeId ? [input.homeId] : []),\n ]),\n ],\n formIds: input.targets.forms.map((form) => form.id),\n homeScreenIds: input.targets.homeIds,\n codeBuilt: true,\n }\n const stored = aiLayoutStoredTree(compiled.tree, 'layout', context)\n if (stored.ok === false) {\n console.error('ai layout frame: a compiled layout did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This layout could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a frame every published page shows.\n const navPages = [...input.targets.pages, ...input.pages]\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n stored.rootId,\n {\n pages: navPages,\n ids: [...navPages.map((page) => page.id), ...input.targets.forms.map((form) => form.id)],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: stored.rootId, nodes },\n 'layout',\n context,\n )\n const violations = [\n ...report.violations,\n ...copy.violations,\n ...input.extend({\n rootId: stored.rootId,\n nodes,\n repairs: [],\n sourceIds: {},\n score: report.score as AiValidatedTree['score'],\n load: report.load,\n }),\n ]\n return {\n value: violations.length\n ? null\n : { rootId: stored.rootId, nodes, load: report.load, dropped },\n violations,\n }\n }\n}\n\n/** What a frame's links resolve against: the site's pages and its form, as a page's do. */\nexport function aiLayoutFrameTargets(\n job: Pick<AiJob, 'brief' | 'inputs'>,\n inventory: AiSiteInventory | null,\n): AiLayoutTargets {\n return aiLayoutPageTargets({ job, inventory, own: [] })\n}\n"],"names":["aiLayoutDesignChoices","aiCompileLayoutFrame","AI_LAYOUT_LANGUAGE_TEXT","aiReadLayoutFrame","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","aiHomeScreenIds","AI_STEP_TIERS","validateAiDoctrineTree","aiSiteKindDesignLines","aiJobStepBudget","aiJobBriefLine","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","aiLayoutSitePages","AI_LAYOUT_FRAME_THINKING_TOKENS","aiLayoutPageTargets","AI_SITE_NAME_TOKEN","AI_JOB_LAYOUT_LANGUAGE_TOKENS","AI_JOB_LAYOUT_LANGUAGE_BUDGET","tier","maxTokens","AI_LAYOUT_FRAME_KIND","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutSiteName","job","given","inputs","trim","aiLayoutNavPages","inventory","planned","own","screens","filter","row","template","map","id","label","name","slug","siteHome","some","page","slice","pages","length","home","find","aiLayoutHomeId","aiLayoutFramePrompt","input","targets","siteName","forms","formPageId","$id","aiLayoutFrameCheck","answers","answer","last","frame","headerAlign","header","align","compiled","homeId","navPages","closesDark","design","coverClose","cart","listings","listing","kind","context","screenIds","Set","formIds","form","homeScreenIds","homeIds","codeBuilt","stored","tree","ok","console","error","value","violations","rule","code","message","detail","copy","nodes","rootId","ids","dropped","report","extend","repairs","sourceIds","score","load","aiLayoutFrameTargets"],"mappings":"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"}
@@ -62,6 +62,18 @@ export declare function aiLayoutSitePagesOfPlan(screens: readonly AiBuildPlanScr
62
62
  }): AiLayoutSitePage[];
63
63
  /** The planned pages a layout unit's inputs carry; none for any other layout. */
64
64
  export declare function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[];
65
+ /**
66
+ * The unit input naming the planned pages a start merged into its blog
67
+ * (AGL-3676): a page that stood in for it, whose links go to the blog.
68
+ */
69
+ export declare const AI_LAYOUT_SITE_ALIASES_INPUT = "siteAliases";
70
+ /** A planned page merged into the blog: its id, linked as the blog's path, and the title it had. */
71
+ export interface AiLayoutSiteAlias extends AiLayoutSitePage {
72
+ href: string;
73
+ standsInFor: string;
74
+ }
75
+ /** The merged pages a unit's inputs carry; none for any other unit. */
76
+ export declare function aiLayoutSiteAliases(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSiteAlias[];
65
77
  /**
66
78
  * What the layout's user turn says about those pages: that the platform
67
79
  * writes the navigation, so the model builds none and leaves no empty element
@@ -78,6 +78,26 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
78
78
  }
79
79
  return pages.slice(0, AI_LAYOUT_SITE_PAGES_MAX);
80
80
  }
81
+ /**
82
+ * The unit input naming the planned pages a start merged into its blog
83
+ * (AGL-3676): a page that stood in for it, whose links go to the blog.
84
+ */ export const AI_LAYOUT_SITE_ALIASES_INPUT = 'siteAliases';
85
+ /** The merged pages a unit's inputs carry; none for any other unit. */ export function aiLayoutSiteAliases(inputs) {
86
+ const raw = inputs == null ? void 0 : inputs[AI_LAYOUT_SITE_ALIASES_INPUT];
87
+ if (!Array.isArray(raw)) return [];
88
+ return raw.flatMap((entry)=>{
89
+ const { id, label, slug, href, standsInFor } = entry != null ? entry : {};
90
+ return typeof id === 'string' && id && typeof label === 'string' && typeof href === 'string' && /^\/[a-z0-9-]+$/.test(href) && typeof standsInFor === 'string' ? [
91
+ {
92
+ id,
93
+ label,
94
+ slug: typeof slug === 'string' ? slug : href,
95
+ href,
96
+ standsInFor
97
+ }
98
+ ] : [];
99
+ });
100
+ }
81
101
  /**
82
102
  * What the layout's user turn says about those pages: that the platform
83
103
  * writes the navigation, so the model builds none and leaves no empty element
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-site-pages.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 { normalizeScreenSlug, SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AiBuildPlanScreen } from '../model/ai-build-plan'\n\n/**\n * The navigation of a layout a site scaffold builds before its pages (AGL-3596).\n *\n * A scaffold builds its layout FIRST, because every page renders inside it,\n * so when the layout is generated none of the site's pages exists yet: the\n * model cannot link them, and the layout instructions forbid inventing a page\n * id. A plan that gave the layout a nav region then left the model one way to\n * keep the promise — a nav element with nothing in it — which rule 16 refuses\n * as an empty container, twice, and the guided start stopped there.\n *\n * The scaffold does know the pages: their ids are minted when the plan is\n * kept (`AiBuildPlanScreen.id`), and each page unit writes its draft under\n * that id. So the layout unit is handed them (`sitePages`), and the header's\n * navigation is written by the platform rather than asked of the model: one\n * Page Link per planned page, inside the nav element the model left (filled,\n * never left empty) or a nav row the platform adds to the Toolbar Content.\n * The completed tree is what the doctrine checks and what is written, so rule\n * 16 holds unchanged — nothing it sees is empty — and the publish step, which\n * adds only the entries a header does not already carry, adds nothing twice.\n */\n\n/** The unit input a site layout's planned pages travel under. */\nexport const AI_LAYOUT_SITE_PAGES_INPUT = 'sitePages'\n\n/** The most pages the header links, as the layout instructions cap it. */\nexport const AI_LAYOUT_SITE_PAGES_MAX = 7\n\n/** A page the scaffold builds after the layout, as the layout links it. */\nexport interface AiLayoutSitePage {\n id: string\n label: string\n slug: string\n /** The path of a destination that is no page, such as the blog a start writes (AGL-3660). */\n href?: string\n}\n\n/** Whether a slug is the site's root, the home page. */\nexport function aiLayoutIsHomeSlug(slug: string): boolean {\n return slug.replace(/^\\/+|\\/+$/g, '') === ''\n}\n\n/** A page that confirms what a visitor just did, which no navigation links. */\nconst CONFIRMATION = /thank|confirm|success|submitted/i\n\n/**\n * The pages a plan puts in the navigation, record templates aside, the home\n * page first whatever its `nav` flag says (AGL-3660): a guided start's plan\n * marked its Home `nav: false`, and the header linked only Contact. Every\n * other page keeps its `nav` flag, except in a guided start (`guided`), whose\n * pages are the ones the person asked for: each is linked, but a\n * confirmation page — a thank-you, a \"submitted\" — stays out.\n */\nexport function aiLayoutSitePagesOfPlan(\n screens: readonly AiBuildPlanScreen[],\n options: { guided?: boolean } = {},\n): AiLayoutSitePage[] {\n const linked = (screen: AiBuildPlanScreen) =>\n aiLayoutIsHomeSlug(screen.slug) ||\n (options.guided ? !CONFIRMATION.test(`${screen.slug} ${screen.title}`) : !!screen.nav)\n const pages = screens\n .filter((screen) => !screen.record && typeof screen.id === 'string' && screen.id && linked(screen))\n .map((screen) => ({ id: screen.id as string, label: screen.title.trim(), slug: screen.slug }))\n .filter((page) => page.label)\n return [...pages.filter((page) => aiLayoutIsHomeSlug(page.slug)), ...pages.filter((page) => !aiLayoutIsHomeSlug(page.slug))].slice(\n 0,\n AI_LAYOUT_SITE_PAGES_MAX,\n )\n}\n\n/** The planned pages a layout unit's inputs carry; none for any other layout. */\nexport function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[] {\n const raw = inputs?.[AI_LAYOUT_SITE_PAGES_INPUT]\n if (!Array.isArray(raw)) return []\n const pages: AiLayoutSitePage[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, label, slug, href } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id || typeof label !== 'string' || !label.trim()) continue\n if (pages.some((page) => page.id === id)) continue\n pages.push({\n id,\n label: label.trim(),\n slug: typeof slug === 'string' ? slug : '',\n // Only a path on this site, never an address elsewhere.\n ...(typeof href === 'string' && /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/.test(href) ? { href } : {}),\n })\n }\n return pages.slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/**\n * What the layout's user turn says about those pages: that the platform\n * writes the navigation, so the model builds none and leaves no empty element\n * for it. The same promise the completion below keeps.\n */\nexport function aiLayoutSitePagesLines(pages: readonly AiLayoutSitePage[]): string[] {\n if (!pages.length) return []\n return [\n `This site’s pages are built after this layout: ${pages\n .map((page) => `${page.label} (${screenPath(page.slug)})`)\n .join(', ')}.`,\n 'The platform writes the header’s navigation to those pages itself, as a nav row of Page Links inside the Toolbar Content. Build no navigation links to them and no nav element for them: leave no empty element where the links will go.',\n ]\n}\n\nfunction screenPath(slug: string): string {\n const normalized = normalizeScreenSlug(slug)\n if (!normalized || normalized === SCREEN_ROOT_PATH) return '/'\n return `/${normalized.replace(/^\\/+/, '')}`\n}\n\ntype RawNode = { componentId?: unknown; props?: Record<string, unknown>; nodes?: unknown; [key: string]: unknown }\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\nconst childrenOf = (node: RawNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\nconst elementOf = (node: RawNode | undefined): string => {\n const value = node?.props?.['component'] ?? node?.props?.['element']\n return typeof value === 'string' ? value : ''\n}\n\nconst LINK_COMPONENTS = new Set(['muiScreenLink', 'muiButton'])\n\n/**\n * The model's layout with the header's navigation written in: a Page Link to\n * every planned page the header does not already link, placed in the first\n * nav element above the Layout Slot, else in a nav row the platform appends\n * to the Toolbar Content; and every OTHER nav element the model left empty\n * given the same links, so no nav is ever empty. A link to the site's current\n * home page is pointed at the planned home, which replaces it when the site\n * is published. Anything that is not a node map, or has no planned pages, is\n * returned as it came, for the doctrine to judge.\n */\nexport function aiLayoutWithSitePages(\n tree: unknown,\n pages: readonly AiLayoutSitePage[],\n options: { homeScreenIds?: readonly string[] } = {},\n): unknown {\n if (!pages.length || !isRecord(tree) || !isRecord(tree['nodes'])) return tree\n const rootId = typeof tree['rootId'] === 'string' ? tree['rootId'] : null\n const source = tree['nodes'] as Record<string, unknown>\n if (!rootId || !isRecord(source[rootId])) return tree\n const nodes: Record<string, RawNode> = {}\n for (const [id, node] of Object.entries(source)) {\n if (!isRecord(node)) continue\n nodes[id] = {\n ...(node as RawNode),\n ...(isRecord(node['props']) ? { props: { ...(node['props'] as Record<string, unknown>) } } : {}),\n ...(Array.isArray(node['nodes']) ? { nodes: [...(node['nodes'] as unknown[])] } : {}),\n }\n }\n\n // Document order, and where the header ends: the Layout Slot.\n const order: string[] = []\n const seen = new Set<string>()\n const visit = (id: string) => {\n if (seen.has(id) || !nodes[id]) return\n seen.add(id)\n order.push(id)\n for (const child of childrenOf(nodes[id])) visit(child)\n }\n visit(rootId)\n const slotAt = order.findIndex((id) => nodes[id].componentId === 'layoutSlot')\n const header = slotAt === -1 ? order : order.slice(0, slotAt)\n\n // The site's current home is replaced by the planned one at publish.\n const plannedHome = pages.find((page) => screenPath(page.slug) === '/')\n const oldHomes = new Set(options.homeScreenIds ?? [])\n let changed = false\n if (plannedHome) {\n for (const id of order) {\n const node = nodes[id]\n const screenId = node.props?.['screenId']\n if (LINK_COMPONENTS.has(String(node.componentId)) && typeof screenId === 'string' && oldHomes.has(screenId)) {\n node.props = { ...node.props, screenId: plannedHome.id }\n changed = true\n }\n }\n }\n\n const used = new Set(Object.keys(nodes))\n const freshId = (base: string): string => {\n let id = base\n for (let n = 2; used.has(id); n += 1) id = `${base}${n}`\n used.add(id)\n return id\n }\n const linkNodes = (entries: readonly AiLayoutSitePage[]): string[] =>\n entries.map((page) => {\n const id = freshId(`aiNavLink_${page.id.replace(/[^A-Za-z0-9_]/g, '')}`)\n nodes[id] = {\n componentId: 'muiScreenLink',\n props: {\n children: page.label.slice(0, 40),\n ...(page.href ? { href: page.href } : { screenId: page.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n }\n return id\n })\n\n const linkedInHeader = new Set(\n header\n .map((id) => nodes[id])\n .filter((node) => LINK_COMPONENTS.has(String(node.componentId)))\n .flatMap((node) => [node.props?.['screenId'], node.props?.['href']])\n .filter((value): value is string => typeof value === 'string'),\n )\n const missing = pages.filter((page) => !linkedInHeader.has(page.href ?? page.id))\n const navs = order.filter((id) => elementOf(nodes[id]) === 'nav')\n const headerNav = navs.find((id) => header.includes(id)) ?? null\n\n if (missing.length) {\n if (headerNav) {\n nodes[headerNav].nodes = [...childrenOf(nodes[headerNav]), ...linkNodes(missing)]\n changed = true\n } else {\n const toolbar = header.find((id) => nodes[id].componentId === 'muiToolbar')\n if (toolbar) {\n const navId = freshId('aiSiteNav')\n nodes[navId] = {\n componentId: 'muiStack',\n props: { component: 'nav', direction: 'row', alignItems: 'center' },\n sx: { gap: 2 },\n nodes: linkNodes(missing),\n }\n nodes[toolbar].nodes = [...childrenOf(nodes[toolbar]), navId]\n changed = true\n }\n }\n }\n // A nav element the model left empty anywhere else — a footer's, say — links the same pages.\n for (const id of navs) {\n if (id === headerNav && missing.length) continue\n if (childrenOf(nodes[id]).some((child) => nodes[child])) continue\n nodes[id].nodes = linkNodes(pages)\n changed = true\n }\n return changed ? { ...tree, nodes } : tree\n}\n"],"names":["normalizeScreenSlug","SCREEN_ROOT_PATH","AI_LAYOUT_SITE_PAGES_INPUT","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","slug","replace","CONFIRMATION","aiLayoutSitePagesOfPlan","screens","options","linked","screen","guided","test","title","nav","pages","filter","record","id","map","label","trim","page","slice","aiLayoutSitePages","inputs","raw","Array","isArray","entry","href","some","push","aiLayoutSitePagesLines","length","screenPath","join","normalized","isRecord","value","Boolean","childrenOf","node","nodes","elementOf","props","LINK_COMPONENTS","Set","aiLayoutWithSitePages","tree","navs","rootId","source","Object","entries","order","seen","visit","has","add","child","slotAt","findIndex","componentId","header","plannedHome","find","oldHomes","homeScreenIds","changed","screenId","String","used","keys","freshId","base","n","linkNodes","children","renderAs","color","linkedInHeader","flatMap","missing","headerNav","includes","toolbar","navId","component","direction","alignItems","sx","gap"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-site-pages.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 { normalizeScreenSlug, SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AiBuildPlanScreen } from '../model/ai-build-plan'\n\n/**\n * The navigation of a layout a site scaffold builds before its pages (AGL-3596).\n *\n * A scaffold builds its layout FIRST, because every page renders inside it,\n * so when the layout is generated none of the site's pages exists yet: the\n * model cannot link them, and the layout instructions forbid inventing a page\n * id. A plan that gave the layout a nav region then left the model one way to\n * keep the promise — a nav element with nothing in it — which rule 16 refuses\n * as an empty container, twice, and the guided start stopped there.\n *\n * The scaffold does know the pages: their ids are minted when the plan is\n * kept (`AiBuildPlanScreen.id`), and each page unit writes its draft under\n * that id. So the layout unit is handed them (`sitePages`), and the header's\n * navigation is written by the platform rather than asked of the model: one\n * Page Link per planned page, inside the nav element the model left (filled,\n * never left empty) or a nav row the platform adds to the Toolbar Content.\n * The completed tree is what the doctrine checks and what is written, so rule\n * 16 holds unchanged — nothing it sees is empty — and the publish step, which\n * adds only the entries a header does not already carry, adds nothing twice.\n */\n\n/** The unit input a site layout's planned pages travel under. */\nexport const AI_LAYOUT_SITE_PAGES_INPUT = 'sitePages'\n\n/** The most pages the header links, as the layout instructions cap it. */\nexport const AI_LAYOUT_SITE_PAGES_MAX = 7\n\n/** A page the scaffold builds after the layout, as the layout links it. */\nexport interface AiLayoutSitePage {\n id: string\n label: string\n slug: string\n /** The path of a destination that is no page, such as the blog a start writes (AGL-3660). */\n href?: string\n}\n\n/** Whether a slug is the site's root, the home page. */\nexport function aiLayoutIsHomeSlug(slug: string): boolean {\n return slug.replace(/^\\/+|\\/+$/g, '') === ''\n}\n\n/** A page that confirms what a visitor just did, which no navigation links. */\nconst CONFIRMATION = /thank|confirm|success|submitted/i\n\n/**\n * The pages a plan puts in the navigation, record templates aside, the home\n * page first whatever its `nav` flag says (AGL-3660): a guided start's plan\n * marked its Home `nav: false`, and the header linked only Contact. Every\n * other page keeps its `nav` flag, except in a guided start (`guided`), whose\n * pages are the ones the person asked for: each is linked, but a\n * confirmation page — a thank-you, a \"submitted\" — stays out.\n */\nexport function aiLayoutSitePagesOfPlan(\n screens: readonly AiBuildPlanScreen[],\n options: { guided?: boolean } = {},\n): AiLayoutSitePage[] {\n const linked = (screen: AiBuildPlanScreen) =>\n aiLayoutIsHomeSlug(screen.slug) ||\n (options.guided ? !CONFIRMATION.test(`${screen.slug} ${screen.title}`) : !!screen.nav)\n const pages = screens\n .filter((screen) => !screen.record && typeof screen.id === 'string' && screen.id && linked(screen))\n .map((screen) => ({ id: screen.id as string, label: screen.title.trim(), slug: screen.slug }))\n .filter((page) => page.label)\n return [...pages.filter((page) => aiLayoutIsHomeSlug(page.slug)), ...pages.filter((page) => !aiLayoutIsHomeSlug(page.slug))].slice(\n 0,\n AI_LAYOUT_SITE_PAGES_MAX,\n )\n}\n\n/** The planned pages a layout unit's inputs carry; none for any other layout. */\nexport function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[] {\n const raw = inputs?.[AI_LAYOUT_SITE_PAGES_INPUT]\n if (!Array.isArray(raw)) return []\n const pages: AiLayoutSitePage[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, label, slug, href } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id || typeof label !== 'string' || !label.trim()) continue\n if (pages.some((page) => page.id === id)) continue\n pages.push({\n id,\n label: label.trim(),\n slug: typeof slug === 'string' ? slug : '',\n // Only a path on this site, never an address elsewhere.\n ...(typeof href === 'string' && /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/.test(href) ? { href } : {}),\n })\n }\n return pages.slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/**\n * The unit input naming the planned pages a start merged into its blog\n * (AGL-3676): a page that stood in for it, whose links go to the blog.\n */\nexport const AI_LAYOUT_SITE_ALIASES_INPUT = 'siteAliases'\n\n/** A planned page merged into the blog: its id, linked as the blog's path, and the title it had. */\nexport interface AiLayoutSiteAlias extends AiLayoutSitePage {\n href: string\n standsInFor: string\n}\n\n/** The merged pages a unit's inputs carry; none for any other unit. */\nexport function aiLayoutSiteAliases(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSiteAlias[] {\n const raw = inputs?.[AI_LAYOUT_SITE_ALIASES_INPUT]\n if (!Array.isArray(raw)) return []\n return raw.flatMap((entry) => {\n const { id, label, slug, href, standsInFor } = (entry ?? {}) as Record<string, unknown>\n return typeof id === 'string' &&\n id &&\n typeof label === 'string' &&\n typeof href === 'string' &&\n /^\\/[a-z0-9-]+$/.test(href) &&\n typeof standsInFor === 'string'\n ? [{ id, label, slug: typeof slug === 'string' ? slug : href, href, standsInFor }]\n : []\n })\n}\n\n/**\n * What the layout's user turn says about those pages: that the platform\n * writes the navigation, so the model builds none and leaves no empty element\n * for it. The same promise the completion below keeps.\n */\nexport function aiLayoutSitePagesLines(pages: readonly AiLayoutSitePage[]): string[] {\n if (!pages.length) return []\n return [\n `This site’s pages are built after this layout: ${pages\n .map((page) => `${page.label} (${screenPath(page.slug)})`)\n .join(', ')}.`,\n 'The platform writes the header’s navigation to those pages itself, as a nav row of Page Links inside the Toolbar Content. Build no navigation links to them and no nav element for them: leave no empty element where the links will go.',\n ]\n}\n\nfunction screenPath(slug: string): string {\n const normalized = normalizeScreenSlug(slug)\n if (!normalized || normalized === SCREEN_ROOT_PATH) return '/'\n return `/${normalized.replace(/^\\/+/, '')}`\n}\n\ntype RawNode = { componentId?: unknown; props?: Record<string, unknown>; nodes?: unknown; [key: string]: unknown }\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\nconst childrenOf = (node: RawNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\nconst elementOf = (node: RawNode | undefined): string => {\n const value = node?.props?.['component'] ?? node?.props?.['element']\n return typeof value === 'string' ? value : ''\n}\n\nconst LINK_COMPONENTS = new Set(['muiScreenLink', 'muiButton'])\n\n/**\n * The model's layout with the header's navigation written in: a Page Link to\n * every planned page the header does not already link, placed in the first\n * nav element above the Layout Slot, else in a nav row the platform appends\n * to the Toolbar Content; and every OTHER nav element the model left empty\n * given the same links, so no nav is ever empty. A link to the site's current\n * home page is pointed at the planned home, which replaces it when the site\n * is published. Anything that is not a node map, or has no planned pages, is\n * returned as it came, for the doctrine to judge.\n */\nexport function aiLayoutWithSitePages(\n tree: unknown,\n pages: readonly AiLayoutSitePage[],\n options: { homeScreenIds?: readonly string[] } = {},\n): unknown {\n if (!pages.length || !isRecord(tree) || !isRecord(tree['nodes'])) return tree\n const rootId = typeof tree['rootId'] === 'string' ? tree['rootId'] : null\n const source = tree['nodes'] as Record<string, unknown>\n if (!rootId || !isRecord(source[rootId])) return tree\n const nodes: Record<string, RawNode> = {}\n for (const [id, node] of Object.entries(source)) {\n if (!isRecord(node)) continue\n nodes[id] = {\n ...(node as RawNode),\n ...(isRecord(node['props']) ? { props: { ...(node['props'] as Record<string, unknown>) } } : {}),\n ...(Array.isArray(node['nodes']) ? { nodes: [...(node['nodes'] as unknown[])] } : {}),\n }\n }\n\n // Document order, and where the header ends: the Layout Slot.\n const order: string[] = []\n const seen = new Set<string>()\n const visit = (id: string) => {\n if (seen.has(id) || !nodes[id]) return\n seen.add(id)\n order.push(id)\n for (const child of childrenOf(nodes[id])) visit(child)\n }\n visit(rootId)\n const slotAt = order.findIndex((id) => nodes[id].componentId === 'layoutSlot')\n const header = slotAt === -1 ? order : order.slice(0, slotAt)\n\n // The site's current home is replaced by the planned one at publish.\n const plannedHome = pages.find((page) => screenPath(page.slug) === '/')\n const oldHomes = new Set(options.homeScreenIds ?? [])\n let changed = false\n if (plannedHome) {\n for (const id of order) {\n const node = nodes[id]\n const screenId = node.props?.['screenId']\n if (LINK_COMPONENTS.has(String(node.componentId)) && typeof screenId === 'string' && oldHomes.has(screenId)) {\n node.props = { ...node.props, screenId: plannedHome.id }\n changed = true\n }\n }\n }\n\n const used = new Set(Object.keys(nodes))\n const freshId = (base: string): string => {\n let id = base\n for (let n = 2; used.has(id); n += 1) id = `${base}${n}`\n used.add(id)\n return id\n }\n const linkNodes = (entries: readonly AiLayoutSitePage[]): string[] =>\n entries.map((page) => {\n const id = freshId(`aiNavLink_${page.id.replace(/[^A-Za-z0-9_]/g, '')}`)\n nodes[id] = {\n componentId: 'muiScreenLink',\n props: {\n children: page.label.slice(0, 40),\n ...(page.href ? { href: page.href } : { screenId: page.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n }\n return id\n })\n\n const linkedInHeader = new Set(\n header\n .map((id) => nodes[id])\n .filter((node) => LINK_COMPONENTS.has(String(node.componentId)))\n .flatMap((node) => [node.props?.['screenId'], node.props?.['href']])\n .filter((value): value is string => typeof value === 'string'),\n )\n const missing = pages.filter((page) => !linkedInHeader.has(page.href ?? page.id))\n const navs = order.filter((id) => elementOf(nodes[id]) === 'nav')\n const headerNav = navs.find((id) => header.includes(id)) ?? null\n\n if (missing.length) {\n if (headerNav) {\n nodes[headerNav].nodes = [...childrenOf(nodes[headerNav]), ...linkNodes(missing)]\n changed = true\n } else {\n const toolbar = header.find((id) => nodes[id].componentId === 'muiToolbar')\n if (toolbar) {\n const navId = freshId('aiSiteNav')\n nodes[navId] = {\n componentId: 'muiStack',\n props: { component: 'nav', direction: 'row', alignItems: 'center' },\n sx: { gap: 2 },\n nodes: linkNodes(missing),\n }\n nodes[toolbar].nodes = [...childrenOf(nodes[toolbar]), navId]\n changed = true\n }\n }\n }\n // A nav element the model left empty anywhere else — a footer's, say — links the same pages.\n for (const id of navs) {\n if (id === headerNav && missing.length) continue\n if (childrenOf(nodes[id]).some((child) => nodes[child])) continue\n nodes[id].nodes = linkNodes(pages)\n changed = true\n }\n return changed ? { ...tree, nodes } : tree\n}\n"],"names":["normalizeScreenSlug","SCREEN_ROOT_PATH","AI_LAYOUT_SITE_PAGES_INPUT","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","slug","replace","CONFIRMATION","aiLayoutSitePagesOfPlan","screens","options","linked","screen","guided","test","title","nav","pages","filter","record","id","map","label","trim","page","slice","aiLayoutSitePages","inputs","raw","Array","isArray","entry","href","some","push","AI_LAYOUT_SITE_ALIASES_INPUT","aiLayoutSiteAliases","flatMap","standsInFor","aiLayoutSitePagesLines","length","screenPath","join","normalized","isRecord","value","Boolean","childrenOf","node","nodes","elementOf","props","LINK_COMPONENTS","Set","aiLayoutWithSitePages","tree","navs","rootId","source","Object","entries","order","seen","visit","has","add","child","slotAt","findIndex","componentId","header","plannedHome","find","oldHomes","homeScreenIds","changed","screenId","String","used","keys","freshId","base","n","linkNodes","children","renderAs","color","linkedInHeader","missing","headerNav","includes","toolbar","navId","component","direction","alignItems","sx","gap"],"mappings":"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"}
@@ -108,6 +108,13 @@ export declare function aiLayoutPageTargets(input: {
108
108
  inventory: AiSiteInventory | null;
109
109
  own: ReadonlyArray<string | null | undefined>;
110
110
  }): AiLayoutTargets;
111
+ /**
112
+ * The page check's context with what a section listing the blog's posts
113
+ * binds (AGL-3676): each post's card links its post and shows its cover by
114
+ * the tokens the page fills per post, which a page's store admits whole only
115
+ * where a listing places them.
116
+ */
117
+ export declare function aiLayoutListingContext(context: AiDoctrineTreeContext, targets: AiLayoutTargets): AiDoctrineTreeContext;
111
118
  /** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */
112
119
  export declare function aiLayoutPagePrompt(input: {
113
120
  job: Pick<AiJob, 'brief' | 'inputs' | '$id'>;
@@ -179,7 +186,9 @@ export declare function aiLayoutShownItems(itemIds: readonly (readonly string[])
179
186
  * and an intro over nothing is a section a visitor reads as broken, so it is
180
187
  * asked for again.
181
188
  */
182
- export declare function aiLayoutEmptyItemSections(screen: Pick<AiBuildPlanScreen, 'sections'>, items: readonly number[]): AiDoctrineViolation[];
189
+ export declare function aiLayoutEmptyItemSections(screen: Pick<AiBuildPlanScreen, 'sections'>, items: readonly number[],
190
+ /** Sections whose only items were customer quotes, which no answer may give (AGL-3676). */
191
+ quotesOnly?: readonly number[]): AiDoctrineViolation[];
183
192
  /**
184
193
  * The check a language page answer is held to: each planned section read,
185
194
  * the page compiled, stored and checked whole. Sections that read are kept
@@ -17,7 +17,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  */ import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
18
18
  import { isAiPlanNewRef } from "../model/ai-build-plan.js";
19
19
  import { aiHomeScreenIds } from "../model/ai-site-inventory.js";
20
- import { aiCompileLayoutPage } from "../layout-language/ai-layout-compiler.js";
20
+ import { AI_LAYOUT_POST_ADDRESS_TOKENS, aiCompileLayoutPage } from "../layout-language/ai-layout-compiler.js";
21
+ import { aiLayoutListingAt, aiLayoutListingsOf } from "../layout-language/ai-layout-listings.js";
21
22
  import { AI_LAYOUT_LANGUAGE_TEXT, AI_LAYOUT_PAGE_TOOL, aiReadLayoutPage } from "../layout-language/ai-layout-language.js";
22
23
  import { aiLayoutCopyCheck } from "../layout-language/ai-layout-gaps.js";
23
24
  import { aiLayoutStoredTree } from "../layout-language/ai-layout-store.js";
@@ -30,7 +31,7 @@ import { aiSiteSeed } from "../model/ai-site-look.js";
30
31
  import { aiOriginJobId } from "./ai-job-draft-ids.js";
31
32
  import { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from "./ai-job-budget.js";
32
33
  import { aiJobBriefLine, aiPlanReferenceLines } from "./ai-job-generation.js";
33
- import { aiLayoutIsHomeSlug, aiLayoutSitePages } from "./ai-job-layout-site-pages.js";
34
+ import { aiLayoutIsHomeSlug, aiLayoutSiteAliases, aiLayoutSitePages } from "./ai-job-layout-site-pages.js";
34
35
  import { aiLayoutInventedContactViolations } from "./ai-layout-site-facts.js";
35
36
  /**
36
37
  * A page built in the compact layout language (AGL-3660): the page step's
@@ -134,6 +135,8 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
134
135
  const planned = aiLayoutSitePages(job.inputs);
135
136
  const pages = [
136
137
  ...planned,
138
+ // Planned pages merged into the blog (AGL-3676): a link to one goes to the blog.
139
+ ...aiLayoutSiteAliases(job.inputs).filter((alias)=>!planned.some((page)=>page.id === alias.id)),
137
140
  ...((_ref = inventory == null ? void 0 : inventory.screens) != null ? _ref : []).filter((row)=>!row.template && !planned.some((page)=>page.id === row.id)).map((row)=>({
138
141
  id: row.id,
139
142
  label: row.name,
@@ -155,17 +158,45 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
155
158
  name: component.name,
156
159
  props: component.props
157
160
  })),
158
- facts: aiLayoutFacts(job)
161
+ facts: aiLayoutFacts(job),
162
+ // The site's catalog and blog, and the sections that list them (AGL-3676).
163
+ listings: aiLayoutListingsOf(job.inputs)
159
164
  };
160
165
  }
166
+ /**
167
+ * The page check's context with what a section listing the blog's posts
168
+ * binds (AGL-3676): each post's card links its post and shows its cover by
169
+ * the tokens the page fills per post, which a page's store admits whole only
170
+ * where a listing places them.
171
+ */ export function aiLayoutListingContext(context, targets) {
172
+ var _targets_listings, _context_bindingTokens;
173
+ const listsPosts = ((_targets_listings = targets.listings) != null ? _targets_listings : []).some((listing)=>listing.kind === 'posts' && listing.placements.some((placement)=>placement.screenId === targets.pageId));
174
+ if (!listsPosts) return context;
175
+ return _extends({}, context, {
176
+ bindingTokens: [
177
+ ...new Set([
178
+ ...(_context_bindingTokens = context.bindingTokens) != null ? _context_bindingTokens : [],
179
+ ...AI_LAYOUT_POST_ADDRESS_TOKENS
180
+ ])
181
+ ]
182
+ });
183
+ }
161
184
  /** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */ export function aiLayoutPagePrompt(input) {
162
185
  const { job, plan, screen, targets } = input;
163
186
  const sections = screen.sections.map((section, index)=>{
187
+ var _targets_listings;
164
188
  const places = section.uses.length ? `; places ${section.uses.join(', ')}` : '';
189
+ // A section the site's records fill (AGL-3676): the platform places them,
190
+ // so the design writes the words around them and no group for them.
191
+ const listed = aiLayoutListingAt((_targets_listings = targets.listings) != null ? _targets_listings : [], targets.pageId, index);
192
+ if (listed) {
193
+ return `${index + 1}. "${section.name}"${places}; the platform lists ${listed.listing.name}'s ${listed.listing.kind} here itself, with their photos and links: write only its heading and a line about them, and no cards, list or images for them`;
194
+ }
165
195
  const items = section.items ? `; shows ${section.items} items` : '';
166
196
  return `${index + 1}. "${section.name}"${places}${items}`;
167
197
  });
168
- const pages = targets.pages.map((page)=>`${page.label} (page:${page.id})`);
198
+ // A page merged into the blog is not offered: the blog is, by its own entry.
199
+ const pages = targets.pages.filter((page)=>!page.standsInFor).map((page)=>`${page.label} (page:${page.id})`);
169
200
  const components = targets.components.map((component)=>`${component.name} (${component.id}; props ${Object.keys(component.props).join(', ') || 'none'})`);
170
201
  return [
171
202
  `Page: "${screen.title}" at ${screen.slug}`,
@@ -234,9 +265,10 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
234
265
  * that show none, or too few, once the page is built (AGL-3660). A heading
235
266
  * and an intro over nothing is a section a visitor reads as broken, so it is
236
267
  * asked for again.
237
- */ export function aiLayoutEmptyItemSections(screen, items) {
268
+ */ export function aiLayoutEmptyItemSections(screen, items, /** Sections whose only items were customer quotes, which no answer may give (AGL-3676). */ quotesOnly = []) {
238
269
  return screen.sections.flatMap((section, index)=>{
239
270
  var _items_index;
271
+ if (quotesOnly.includes(index)) return [];
240
272
  const least = leastItems(section.items);
241
273
  if (!least) return [];
242
274
  const shown = (_items_index = items[index]) != null ? _items_index : 0;
@@ -260,6 +292,7 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
260
292
  var _input_kept, _input_only;
261
293
  const kept = (_input_kept = input.kept) != null ? _input_kept : input.screen.sections.map(()=>null);
262
294
  const fills = (_input_only = input.only) != null ? _input_only : input.screen.sections.map((_, index)=>index);
295
+ const context = aiLayoutListingContext(input.context, input.targets);
263
296
  let answers = 0;
264
297
  return (answer)=>{
265
298
  // The last answer a generation takes has its gaps taken out rather than asked about again.
@@ -297,7 +330,7 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
297
330
  }, input.design ? {
298
331
  design: input.design
299
332
  } : {}));
300
- const stored = aiLayoutStoredTree(compiled.tree, 'screen', input.context, input.sectionIds);
333
+ const stored = aiLayoutStoredTree(compiled.tree, 'screen', context, input.sectionIds);
301
334
  if (stored.ok === false) {
302
335
  // The compiler writes only palette trees, which the property spec holds;
303
336
  // a refusal here is the platform's, and is reported as one.
@@ -333,7 +366,7 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
333
366
  const report = validateAiDoctrineTree({
334
367
  rootId: CANVAS_ROOT_ELEMENT_ID,
335
368
  nodes
336
- }, 'page', _extends({}, input.context, {
369
+ }, 'page', _extends({}, context, {
337
370
  scrollTargetIds: input.sectionIds,
338
371
  // The layout language draws its own picture cards (AGL-3660).
339
372
  repeatsCompiled: true
@@ -346,7 +379,7 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
346
379
  const violations = [
347
380
  ...report.violations,
348
381
  ...copy.violations,
349
- ...aiLayoutEmptyItemSections(input.screen, items),
382
+ ...aiLayoutEmptyItemSections(input.screen, items, compiled.quotesOnly),
350
383
  ...aiLayoutInventedContactViolations({
351
384
  rootId: CANVAS_ROOT_ELEMENT_ID,
352
385
  nodes: nodes
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-language.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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { isAiPlanNewRef, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { aiCompileLayoutPage } from '../layout-language/ai-layout-compiler'\nimport {\n AI_LAYOUT_LANGUAGE_TEXT,\n AI_LAYOUT_PAGE_TOOL,\n aiReadLayoutPage,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport {\n runValidatedGeneration,\n type AiGenerationCheck,\n type AiGenerationSpend,\n type AiValidatedGeneration,\n} from '../runtime/ai-doctrine'\nimport type { AiUsage } from '../providers/contract'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineNode,\n type AiDoctrineTreeContext,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiSiteKindDesignLines, aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { aiSiteSeed } from '../model/ai-site-look'\nimport type { AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiLayoutIsHomeSlug, aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations } from './ai-layout-site-facts'\n\n/**\n * A page built in the compact layout language (AGL-3660): the page step's\n * pass for a site's page units — a guided start's and a build's — in place of\n * the raw-tree section passes.\n *\n * ONE answer a page: every planned section, in the language, through\n * `submit_page`. The compiler writes the tree, the palette validator stores\n * it as it stores any generated tree, and the doctrine checks the whole page.\n * A section the answer left out or left empty is the one thing a re-ask asks\n * for: the sections that read are kept, and the second answer fills only\n * what is still missing. Then the page step's last pass runs as it always\n * has — the whole page held to the doctrine again, its listing written, the\n * draft reported — so the item ledger, the credits and Try again work exactly\n * as they did.\n */\n\n/** The unit input that says a page or a layout is built in the layout language. */\nexport const AI_LAYOUT_LANGUAGE_INPUT = 'layoutLanguage'\n\n/** The unit input naming the page of the site that places its form, where it is another page. */\nexport const AI_LAYOUT_FORM_PAGE_INPUT = 'layoutFormPage'\n\n/** Whether this job builds its page or its layout in the layout language. */\nexport function aiJobUsesLayoutLanguage(job: Pick<AiJob, 'inputs'>): boolean {\n return job.inputs?.[AI_LAYOUT_LANGUAGE_INPUT] === true\n}\n\n/**\n * How the language doors think (AGL-3660): adaptively, at an effort named\n * rather than left to the model's default. Zach, 2026-10-07: quality first —\n * a page and a site's frame are designed, not transcribed, and the caching\n * work pays for the thinking. Medium is where Claude Sonnet 5.5, the balanced\n * default, is tuned to start for multistep design work.\n */\nexport const AI_LAYOUT_LANGUAGE_THINKING = { thinking: 'adaptive', effort: 'medium' } as const\n\n/**\n * Room for the thinking a page's answer does before it writes, inside the\n * same `max_tokens` as the answer: thinking is drawn from the ceiling first,\n * so a ceiling sized for the answer alone would cut a thoughtful answer off.\n *\n * Bounded by TIME, not by money: the page step's answer, its re-ask and the\n * follow-up must fit the least time a beat gives a step\n * (`AI_JOB_STEP_MAX_MINIMUM_MS`), and `aiJobStepBudget` quietly lowers every\n * ceiling to what fits. 2,000 here, with the follow-up's 500, is the most\n * that fits whole on the balanced tier; at 5,000 the budget cut a page's\n * ceiling to 879 tokens, under the answer itself.\n */\nexport const AI_LAYOUT_PAGE_THINKING_TOKENS = 2_000\n\n/** The same room for a site's header and footer, whose step has the time for more. */\nexport const AI_LAYOUT_FRAME_THINKING_TOKENS = 4_000\n\n/**\n * The most one page's answer asks for: the answer and the thinking before it.\n * A section in the language is about 120 to 250 tokens with its copy, so the\n * largest page a plan admits — eight sections — fits in 3,000 with room (the\n * live runs of 2026-10-07 wrote 683 to 1,745); the compiler, not the ceiling,\n * keeps a page small.\n */\nexport const AI_JOB_PAGE_LANGUAGE_TOKENS = 3_000 + AI_LAYOUT_PAGE_THINKING_TOKENS\n\n/**\n * The most one section of a page is written in, its copy included: the top of\n * the 120 to 250 tokens a section runs to. The Free site wall prices each\n * planned section at it (AGL-3660).\n */\nexport const AI_LAYOUT_SECTION_MOST_TOKENS = 250\n\n/**\n * The time a language page pass needs: its lookup rounds, its answer and its\n * re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served\n * from, with the step's reads and writes.\n */\n/** The most the follow-up asking only for the sections an answer left out may run to, its thinking included. */\nexport const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1_500 + 500\n\nexport const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_TOKENS,\n // The follow-up for sections an answer left out is one more generation.\n ownReadsMs: aiGenerationWorstCaseOnTierMs({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS,\n }),\n})\n\n/** The doctrine scope the language doors are told: values the compiler checks itself. */\nexport const AI_LAYOUT_PAGE_KIND = 'layout-page'\n\n/** The language, as the page door's cached instructions. */\nexport const AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n 'Answer with submit_page: the whole page, one entry in sections for each planned section, in order.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The page of a site plan that places its form: the form the plan creates, or one the site has. */\nexport function aiLayoutFormPageOfPlan(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n formIds: readonly string[] = [],\n): string | null {\n const created = new Set(\n plan.create\n .filter((entry) => entry.kind === 'form')\n .map((entry) => `new:${entry.name}`.toLowerCase()),\n )\n const forms = new Set(formIds)\n const screen = plan.screens.find((entry) =>\n entry.sections.some((section) =>\n section.uses.some((ref) =>\n isAiPlanNewRef(ref) ? created.has(ref.toLowerCase()) : forms.has(ref),\n ),\n ),\n )\n return typeof screen?.id === 'string' ? screen.id : null\n}\n\n/** Every word the job was given, which a page's contact details are held to. */\nexport function aiLayoutFacts(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const values = Object.values(job.inputs ?? {}).filter(\n (value): value is string => typeof value === 'string',\n )\n return [job.brief, ...values].join('\\n')\n}\n\n/** What a page's links resolve against: the site's pages, built and planned, its forms and components. */\nexport function aiLayoutPageTargets(input: {\n job: Pick<AiJob, 'brief' | 'inputs'>\n inventory: AiSiteInventory | null\n own: ReadonlyArray<string | null | undefined>\n}): AiLayoutTargets {\n const { job, inventory } = input\n const own = input.own.filter((id): id is string => !!id)\n const planned = aiLayoutSitePages(job.inputs)\n const pages: AiLayoutPage[] = [\n ...planned,\n ...(inventory?.screens ?? [])\n .filter(\n (row) => !row.template && !planned.some((page) => page.id === row.id),\n )\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug })),\n ]\n const formPage = job.inputs?.[AI_LAYOUT_FORM_PAGE_INPUT]\n return {\n pageId: own[0] ?? null,\n pages: pages.filter((page) => !own.includes(page.id)),\n homeIds: aiHomeScreenIds(inventory),\n forms: (inventory?.forms ?? []).map((form) => ({\n id: form.id,\n name: form.name,\n })),\n formPageId:\n typeof formPage === 'string' && !own.includes(formPage) ? formPage : null,\n components: (inventory?.components ?? []).map((component) => ({\n id: component.id,\n name: component.name,\n props: component.props,\n })),\n facts: aiLayoutFacts(job),\n }\n}\n\n/** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */\nexport function aiLayoutPagePrompt(input: {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n targets: AiLayoutTargets\n reusableComponents: boolean\n /** The sections a re-ask still needs, by plan index; absent on the first ask. */\n missing?: readonly number[]\n}): string {\n const { job, plan, screen, targets } = input\n const sections = screen.sections.map((section, index) => {\n const places = section.uses.length\n ? `; places ${section.uses.join(', ')}`\n : ''\n const items = section.items ? `; shows ${section.items} items` : ''\n return `${index + 1}. \"${section.name}\"${places}${items}`\n })\n const pages = targets.pages.map((page) => `${page.label} (page:${page.id})`)\n const components = targets.components.map(\n (component) =>\n `${component.name} (${component.id}; props ${Object.keys(component.props).join(', ') || 'none'})`,\n )\n return [\n `Page: \"${screen.title}\" at ${screen.slug}`,\n aiJobBriefLine(job),\n // The kind of site the person picked sets how its pages are arranged (AGL-3660).\n ...aiSiteKindDesignLines(job.inputs),\n ...aiPlanReferenceLines(plan),\n `Sections to design, in order:`,\n ...sections,\n pages.length\n ? `Other pages of this site: ${pages.join(', ')}.`\n : 'This site has no other page to link.',\n targets.forms.length\n ? `The site's form: ${targets.forms.map((form) => `${form.name} (${form.id})`).join(', ')}.`\n : 'The site has no saved form: place none.',\n ...(components.length\n ? [`Components this page may place: ${components.join('; ')}.`]\n : []),\n input.reusableComponents\n ? 'Where a section repeats an item, place the component its plan line names with cards whose to is that component.'\n : 'This site keeps no reusable components: draw repeated items as cards, steps, stats or a list.',\n // A per-job word, so two identical briefs are designed apart.\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/**\n * The site a page is designed for (AGL-3660): the kind its job names, the\n * seed its look was drawn with — the same for every page of the site, so the\n * site reads as one design — and whether the page is its home. `null` for a\n * job that names no kind of site, which compiles the compiler's first way.\n */\nexport function aiLayoutDesignOf(job: Pick<AiJob, '$id' | 'inputs'>, slug: string): AiLayoutDesign | null {\n const kind = aiSiteKindOfInputs(job.inputs)\n if (!kind) return null\n const style = job.inputs?.['siteStyle'] as Record<string, unknown> | undefined\n const seed = typeof style?.['seed'] === 'number' && Number.isFinite(style['seed']) ? (style['seed'] as number) >>> 0 : aiSiteSeed(aiOriginJobId(job))\n return { kind: kind.id, seed, home: aiLayoutIsHomeSlug(slug) }\n}\n\n/** A validated language page, stored as the page keeps it. */\nexport interface AiLayoutPageBuilt {\n nodes: NodesMap\n load: AiLoadEstimate | null\n settled: AiLayoutSettlement[]\n /** What the last answer's gaps took out of the page (`ai-layout-gaps.ts`). */\n dropped: string[]\n /** How many repeated items each plan section shows, as built (AGL-3660). */\n items: number[]\n}\n\nexport interface AiLayoutPageCheckInput {\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n /**\n * What earlier answers gave, by plan index, shared between the page's\n * generations: a follow-up asking only for missing sections fills the rest.\n */\n kept?: Array<AiLayoutSection | null>\n /** The plan indices this answer's sections fill, in order; absent, every section. */\n only?: readonly number[]\n /** The site the page is drawn for (`aiLayoutDesignOf`); absent, the compiler's first design. */\n design?: AiLayoutDesign | null\n}\n\n/**\n * The follow-up turn for the sections a page's answer left out (AGL-3660):\n * the page as before, told which sections it already has, and asked for only\n * the missing ones, in order — the one part of a page asked for again.\n */\nexport function aiLayoutMissingSectionsPrompt(input: {\n base: string\n screen: AiBuildPlanScreen\n missing: readonly number[]\n}): string {\n const names = input.missing.map((index) => `${index + 1}. \"${input.screen.sections[index].name}\"`)\n return [\n input.base,\n '',\n `The other sections of this page are already designed. Design ONLY these, in this order, one entry in sections each: ${names.join('; ')}. Give each at least one block.`,\n ].join('\\n')\n}\n\n/** The plan indices a page still lacks, after the answers so far. */\nexport function aiLayoutMissingSections(kept: ReadonlyArray<AiLayoutSection | null>): number[] {\n return kept.flatMap((section, index) => (section ? [] : [index]))\n}\n\n/** The violation a planned section the answer gave nothing usable for is named by. */\nexport const AI_LAYOUT_SECTION_MISSING_CODE = 'layout-section-missing'\n\n/** The code a section planned with items that shows none is refused under (AGL-3660). */\nexport const AI_LAYOUT_SECTION_EMPTY_CODE = 'layout-section-no-items'\n\n/** How many of a section's planned items it must show: one for a short list, two for anything longer. */\nconst leastItems = (planned: number) => (planned >= 3 ? 2 : planned >= 1 ? 1 : 0)\n\n/**\n * How many repeated items each section shows once stored (AGL-3660): the\n * elements the compiler drew for them, under the ids they are stored by,\n * that are still in the page after anything later took words out.\n */\nexport function aiLayoutShownItems(\n itemIds: readonly (readonly string[])[],\n storedIds: Readonly<Record<string, string>>,\n nodes: Readonly<Record<string, unknown>>,\n): number[] {\n return itemIds.map((ids) => ids.filter((id) => storedIds[id] !== undefined && storedIds[id] in nodes).length)\n}\n\n/**\n * The sections a page planned with repeated items — cards, steps, a list —\n * that show none, or too few, once the page is built (AGL-3660). A heading\n * and an intro over nothing is a section a visitor reads as broken, so it is\n * asked for again.\n */\nexport function aiLayoutEmptyItemSections(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n items: readonly number[],\n): AiDoctrineViolation[] {\n return screen.sections.flatMap((section, index): AiDoctrineViolation[] => {\n const least = leastItems(section.items)\n if (!least) return []\n const shown = items[index] ?? 0\n if (shown >= least) return []\n return [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_EMPTY_CODE,\n message: `Section ${index + 1} (\"${section.name}\") is planned with ${section.items} items and shows ${shown || 'none'}.`,\n detail: `Give section ${index + 1} one cards, steps, stats, list or faq block holding its ${section.items} items, each with a title and its words.`,\n },\n ]\n })\n}\n\n/**\n * The check a language page answer is held to: each planned section read,\n * the page compiled, stored and checked whole. Sections that read are kept\n * across the re-ask, so the second answer is asked only for what was missing,\n * and a section it sends again that already read is not taken twice.\n */\nexport function aiLayoutPageCheck(\n input: AiLayoutPageCheckInput,\n): AiGenerationCheck<AiLayoutPageBuilt> {\n const kept: Array<AiLayoutSection | null> = input.kept ?? input.screen.sections.map(() => null)\n const fills = input.only ?? input.screen.sections.map((_, index) => index)\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const reading = aiReadLayoutPage(\n answer,\n fills.length,\n fills.map((index) => input.screen.sections[index]),\n )\n reading.sections.forEach((section, position) => {\n // A section the answer gives is taken as given; one it leaves out keeps\n // what an earlier answer gave, so a re-ask may send only what was missing.\n if (section) kept[fills[position]] = section\n })\n const missing = kept.flatMap((section, index) => (section ? [] : [index]))\n if (missing.length) {\n const names = missing.map(\n (index) => `${index + 1} (\"${input.screen.sections[index].name}\")`,\n )\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_MISSING_CODE,\n message: `The page left out section ${names.join(', ')}.`,\n detail: `Answer again with every section in order; the sections you already designed are kept, so only ${names.join(', ')} ${missing.length === 1 ? 'needs' : 'need'} its design. Give each at least one block.`,\n },\n ],\n }\n }\n const compiled = aiCompileLayoutPage(\n kept as AiLayoutSection[],\n { title: input.screen.title, sections: input.screen.sections },\n input.targets,\n {\n reusableComponents: input.reusableComponents,\n sectionIds: input.sectionIds,\n ...(input.design ? { design: input.design } : {}),\n },\n )\n const stored = aiLayoutStoredTree(\n compiled.tree,\n 'screen',\n input.context,\n input.sectionIds,\n )\n if (stored.ok === false) {\n // The compiler writes only palette trees, which the property spec holds;\n // a refusal here is the platform's, and is reported as one.\n console.error('ai layout page: a compiled page did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This page could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a page that is published as built.\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n CANVAS_ROOT_ELEMENT_ID,\n {\n pages: input.targets.pages,\n ids: [\n ...input.targets.pages.map((page) => page.id),\n ...input.targets.forms.map((form) => form.id),\n ...input.targets.components.map((component) => component.id),\n ...input.sectionIds,\n ...(input.targets.pageId ? [input.targets.pageId] : []),\n ],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes },\n 'page',\n {\n ...input.context,\n scrollTargetIds: input.sectionIds,\n // The layout language draws its own picture cards (AGL-3660).\n repeatsCompiled: true,\n },\n )\n // Each section's items as they are stored, after anything the gaps took out.\n const items = aiLayoutShownItems(compiled.itemIds, stored.storedIds, nodes as unknown as Record<string, unknown>)\n // A street address or opening hours the job was never given is invented\n // (rule 14, AGL-3596), the same check a layout is held to; a phone number\n // or an email the compiler already writes as its gap.\n const violations: AiDoctrineViolation[] = [\n ...report.violations,\n ...copy.violations,\n ...aiLayoutEmptyItemSections(input.screen, items),\n ...aiLayoutInventedContactViolations(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: nodes as unknown as Record<string, AiDoctrineNode> },\n input.targets.facts,\n ),\n ]\n return {\n value: violations.length\n ? null\n : {\n nodes,\n load: report.load,\n settled: [...reading.settled, ...compiled.settled],\n dropped,\n items,\n },\n violations,\n }\n }\n}\n\nexport interface AiLayoutPageRunInput {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n inventory: AiSiteInventory | null\n model: string\n signal?: AbortSignal\n /** Wraps the check, for an eval that records each answer it reads. */\n observe?: (check: AiGenerationCheck<AiLayoutPageBuilt>) => AiGenerationCheck<AiLayoutPageBuilt>\n}\n\n/** Two generations' spend as one: every call is billed. */\nfunction spentTogether(first: AiGenerationSpend, second: AiGenerationSpend): AiGenerationSpend {\n const usage: AiUsage = {\n inputTokens: first.usage.inputTokens + second.usage.inputTokens,\n outputTokens: first.usage.outputTokens + second.usage.outputTokens,\n cacheReadTokens: first.usage.cacheReadTokens + second.usage.cacheReadTokens,\n cacheWriteTokens: first.usage.cacheWriteTokens + second.usage.cacheWriteTokens,\n }\n return {\n attempts: first.attempts + second.attempts,\n usage,\n estCostUsd: Math.round((first.estCostUsd + second.estCostUsd) * 1_000_000) / 1_000_000,\n model: second.model,\n stopReason: second.stopReason,\n effort: second.effort,\n }\n}\n\n/**\n * A whole page in the layout language: one answer for every section, and,\n * where that answer and its re-ask still left a section out, one follow-up\n * asking for only those (AGL-3660). The sections that read are kept between\n * them, so the page is compiled whole once every section is designed.\n */\nexport async function aiRunLayoutPage(input: AiLayoutPageRunInput): Promise<AiValidatedGeneration<AiLayoutPageBuilt>> {\n const kept: Array<AiLayoutSection | null> = input.screen.sections.map(() => null)\n const observe = input.observe ?? ((check) => check)\n const base = aiLayoutPagePrompt({\n job: input.job,\n plan: input.plan,\n screen: input.screen,\n targets: input.targets,\n reusableComponents: input.reusableComponents,\n })\n const ask = (content: string, maxTokens: number, only?: readonly number[]) =>\n runValidatedGeneration<AiLayoutPageBuilt>(AI_LAYOUT_PAGE_KIND, {\n step: 'job.page',\n model: input.model,\n instructions: AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS,\n inventory: input.inventory,\n messages: [{ role: 'user', content }],\n tool: AI_LAYOUT_PAGE_TOOL,\n maxTokens,\n cutOff: { noun: 'page', smaller: 'Write shorter copy, and fewer items in each group.' },\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: observe(\n aiLayoutPageCheck({\n screen: input.screen,\n sectionIds: input.sectionIds,\n targets: input.targets,\n context: input.context,\n reusableComponents: input.reusableComponents,\n kept,\n design: aiLayoutDesignOf(input.job, input.screen.slug),\n ...(only ? { only } : {}),\n }),\n ),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const first = await ask(base, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model))\n const missing = aiLayoutMissingSections(kept)\n if (\n first.status !== 'needs_input' ||\n !missing.length ||\n !first.violations.every((violation) => violation.code === AI_LAYOUT_SECTION_MISSING_CODE)\n ) {\n return first\n }\n const second = await ask(\n aiLayoutMissingSectionsPrompt({ base, screen: input.screen, missing }),\n Math.min(AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model)),\n missing,\n )\n return { ...second, ...spentTogether(first, second) }\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","isAiPlanNewRef","aiHomeScreenIds","aiCompileLayoutPage","AI_LAYOUT_LANGUAGE_TEXT","AI_LAYOUT_PAGE_TOOL","aiReadLayoutPage","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","AI_STEP_TIERS","runValidatedGeneration","validateAiDoctrineTree","aiSiteKindDesignLines","aiSiteKindOfInputs","aiSiteSeed","aiOriginJobId","aiGenerationWorstCaseOnTierMs","aiJobStepBudget","aiJobBriefLine","aiPlanReferenceLines","aiLayoutIsHomeSlug","aiLayoutSitePages","aiLayoutInventedContactViolations","AI_LAYOUT_LANGUAGE_INPUT","AI_LAYOUT_FORM_PAGE_INPUT","aiJobUsesLayoutLanguage","job","inputs","AI_LAYOUT_LANGUAGE_THINKING","thinking","effort","AI_LAYOUT_PAGE_THINKING_TOKENS","AI_LAYOUT_FRAME_THINKING_TOKENS","AI_JOB_PAGE_LANGUAGE_TOKENS","AI_LAYOUT_SECTION_MOST_TOKENS","AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS","AI_JOB_PAGE_LANGUAGE_BUDGET","tier","maxTokens","ownReadsMs","AI_LAYOUT_PAGE_KIND","AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutFormPageOfPlan","plan","formIds","created","Set","create","filter","entry","kind","map","name","toLowerCase","forms","screen","screens","find","sections","some","section","uses","ref","has","id","aiLayoutFacts","values","Object","value","brief","aiLayoutPageTargets","input","own","inventory","planned","pages","row","template","page","label","slug","formPage","pageId","includes","homeIds","form","formPageId","components","component","props","facts","aiLayoutPagePrompt","targets","index","places","length","items","keys","title","reusableComponents","$id","slice","aiLayoutDesignOf","style","seed","Number","isFinite","home","aiLayoutMissingSectionsPrompt","names","missing","base","aiLayoutMissingSections","kept","flatMap","AI_LAYOUT_SECTION_MISSING_CODE","AI_LAYOUT_SECTION_EMPTY_CODE","leastItems","aiLayoutShownItems","itemIds","storedIds","nodes","ids","undefined","aiLayoutEmptyItemSections","least","shown","rule","code","message","detail","aiLayoutPageCheck","fills","only","_","answers","answer","last","reading","forEach","position","violations","compiled","sectionIds","design","stored","tree","context","ok","console","error","copy","dropped","report","rootId","scrollTargetIds","repeatsCompiled","load","settled","spentTogether","first","second","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","attempts","estCostUsd","Math","round","model","stopReason","aiRunLayoutPage","observe","check","ask","content","step","instructions","messages","role","tool","cutOff","noun","smaller","signal","status","every","violation","min"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-page-language.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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { isAiPlanNewRef, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n aiHomeScreenIds,\n type AiSiteInventory,\n} from '../model/ai-site-inventory'\nimport { AI_LAYOUT_POST_ADDRESS_TOKENS, aiCompileLayoutPage } from '../layout-language/ai-layout-compiler'\nimport { aiLayoutListingAt, aiLayoutListingsOf } from '../layout-language/ai-layout-listings'\nimport {\n AI_LAYOUT_LANGUAGE_TEXT,\n AI_LAYOUT_PAGE_TOOL,\n aiReadLayoutPage,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from '../layout-language/ai-layout-language'\nimport type {\n AiLayoutPage,\n AiLayoutTargets,\n} from '../layout-language/ai-layout-links'\nimport { aiLayoutCopyCheck } from '../layout-language/ai-layout-gaps'\nimport { aiLayoutStoredTree } from '../layout-language/ai-layout-store'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport {\n runValidatedGeneration,\n type AiGenerationCheck,\n type AiGenerationSpend,\n type AiValidatedGeneration,\n} from '../runtime/ai-doctrine'\nimport type { AiUsage } from '../providers/contract'\nimport {\n validateAiDoctrineTree,\n type AiDoctrineNode,\n type AiDoctrineTreeContext,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport { aiSiteKindDesignLines, aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { aiSiteSeed } from '../model/ai-site-look'\nimport type { AiLayoutDesign } from '../layout-language/ai-layout-design'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiGenerationWorstCaseOnTierMs, aiJobStepBudget } from './ai-job-budget'\nimport { aiJobBriefLine, aiPlanReferenceLines } from './ai-job-generation'\nimport { aiLayoutIsHomeSlug, aiLayoutSiteAliases, aiLayoutSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations } from './ai-layout-site-facts'\n\n/**\n * A page built in the compact layout language (AGL-3660): the page step's\n * pass for a site's page units — a guided start's and a build's — in place of\n * the raw-tree section passes.\n *\n * ONE answer a page: every planned section, in the language, through\n * `submit_page`. The compiler writes the tree, the palette validator stores\n * it as it stores any generated tree, and the doctrine checks the whole page.\n * A section the answer left out or left empty is the one thing a re-ask asks\n * for: the sections that read are kept, and the second answer fills only\n * what is still missing. Then the page step's last pass runs as it always\n * has — the whole page held to the doctrine again, its listing written, the\n * draft reported — so the item ledger, the credits and Try again work exactly\n * as they did.\n */\n\n/** The unit input that says a page or a layout is built in the layout language. */\nexport const AI_LAYOUT_LANGUAGE_INPUT = 'layoutLanguage'\n\n/** The unit input naming the page of the site that places its form, where it is another page. */\nexport const AI_LAYOUT_FORM_PAGE_INPUT = 'layoutFormPage'\n\n/** Whether this job builds its page or its layout in the layout language. */\nexport function aiJobUsesLayoutLanguage(job: Pick<AiJob, 'inputs'>): boolean {\n return job.inputs?.[AI_LAYOUT_LANGUAGE_INPUT] === true\n}\n\n/**\n * How the language doors think (AGL-3660): adaptively, at an effort named\n * rather than left to the model's default. Zach, 2026-10-07: quality first —\n * a page and a site's frame are designed, not transcribed, and the caching\n * work pays for the thinking. Medium is where Claude Sonnet 5.5, the balanced\n * default, is tuned to start for multistep design work.\n */\nexport const AI_LAYOUT_LANGUAGE_THINKING = { thinking: 'adaptive', effort: 'medium' } as const\n\n/**\n * Room for the thinking a page's answer does before it writes, inside the\n * same `max_tokens` as the answer: thinking is drawn from the ceiling first,\n * so a ceiling sized for the answer alone would cut a thoughtful answer off.\n *\n * Bounded by TIME, not by money: the page step's answer, its re-ask and the\n * follow-up must fit the least time a beat gives a step\n * (`AI_JOB_STEP_MAX_MINIMUM_MS`), and `aiJobStepBudget` quietly lowers every\n * ceiling to what fits. 2,000 here, with the follow-up's 500, is the most\n * that fits whole on the balanced tier; at 5,000 the budget cut a page's\n * ceiling to 879 tokens, under the answer itself.\n */\nexport const AI_LAYOUT_PAGE_THINKING_TOKENS = 2_000\n\n/** The same room for a site's header and footer, whose step has the time for more. */\nexport const AI_LAYOUT_FRAME_THINKING_TOKENS = 4_000\n\n/**\n * The most one page's answer asks for: the answer and the thinking before it.\n * A section in the language is about 120 to 250 tokens with its copy, so the\n * largest page a plan admits — eight sections — fits in 3,000 with room (the\n * live runs of 2026-10-07 wrote 683 to 1,745); the compiler, not the ceiling,\n * keeps a page small.\n */\nexport const AI_JOB_PAGE_LANGUAGE_TOKENS = 3_000 + AI_LAYOUT_PAGE_THINKING_TOKENS\n\n/**\n * The most one section of a page is written in, its copy included: the top of\n * the 120 to 250 tokens a section runs to. The Free site wall prices each\n * planned section at it (AGL-3660).\n */\nexport const AI_LAYOUT_SECTION_MOST_TOKENS = 250\n\n/**\n * The time a language page pass needs: its lookup rounds, its answer and its\n * re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served\n * from, with the step's reads and writes.\n */\n/** The most the follow-up asking only for the sections an answer left out may run to, its thinking included. */\nexport const AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS = 1_500 + 500\n\nexport const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_TOKENS,\n // The follow-up for sections an answer left out is one more generation.\n ownReadsMs: aiGenerationWorstCaseOnTierMs({\n tier: AI_STEP_TIERS['job.page'],\n maxTokens: AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS,\n }),\n})\n\n/** The doctrine scope the language doors are told: values the compiler checks itself. */\nexport const AI_LAYOUT_PAGE_KIND = 'layout-page'\n\n/** The language, as the page door's cached instructions. */\nexport const AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n AI_LAYOUT_LANGUAGE_TEXT,\n '',\n 'Answer with submit_page: the whole page, one entry in sections for each planned section, in order.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The page of a site plan that places its form: the form the plan creates, or one the site has. */\nexport function aiLayoutFormPageOfPlan(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n formIds: readonly string[] = [],\n): string | null {\n const created = new Set(\n plan.create\n .filter((entry) => entry.kind === 'form')\n .map((entry) => `new:${entry.name}`.toLowerCase()),\n )\n const forms = new Set(formIds)\n const screen = plan.screens.find((entry) =>\n entry.sections.some((section) =>\n section.uses.some((ref) =>\n isAiPlanNewRef(ref) ? created.has(ref.toLowerCase()) : forms.has(ref),\n ),\n ),\n )\n return typeof screen?.id === 'string' ? screen.id : null\n}\n\n/** Every word the job was given, which a page's contact details are held to. */\nexport function aiLayoutFacts(job: Pick<AiJob, 'brief' | 'inputs'>): string {\n const values = Object.values(job.inputs ?? {}).filter(\n (value): value is string => typeof value === 'string',\n )\n return [job.brief, ...values].join('\\n')\n}\n\n/** What a page's links resolve against: the site's pages, built and planned, its forms and components. */\nexport function aiLayoutPageTargets(input: {\n job: Pick<AiJob, 'brief' | 'inputs'>\n inventory: AiSiteInventory | null\n own: ReadonlyArray<string | null | undefined>\n}): AiLayoutTargets {\n const { job, inventory } = input\n const own = input.own.filter((id): id is string => !!id)\n const planned = aiLayoutSitePages(job.inputs)\n const pages: AiLayoutPage[] = [\n ...planned,\n // Planned pages merged into the blog (AGL-3676): a link to one goes to the blog.\n ...aiLayoutSiteAliases(job.inputs).filter((alias) => !planned.some((page) => page.id === alias.id)),\n ...(inventory?.screens ?? [])\n .filter(\n (row) => !row.template && !planned.some((page) => page.id === row.id),\n )\n .map((row) => ({ id: row.id, label: row.name, slug: row.slug })),\n ]\n const formPage = job.inputs?.[AI_LAYOUT_FORM_PAGE_INPUT]\n return {\n pageId: own[0] ?? null,\n pages: pages.filter((page) => !own.includes(page.id)),\n homeIds: aiHomeScreenIds(inventory),\n forms: (inventory?.forms ?? []).map((form) => ({\n id: form.id,\n name: form.name,\n })),\n formPageId:\n typeof formPage === 'string' && !own.includes(formPage) ? formPage : null,\n components: (inventory?.components ?? []).map((component) => ({\n id: component.id,\n name: component.name,\n props: component.props,\n })),\n facts: aiLayoutFacts(job),\n // The site's catalog and blog, and the sections that list them (AGL-3676).\n listings: aiLayoutListingsOf(job.inputs),\n }\n}\n\n/**\n * The page check's context with what a section listing the blog's posts\n * binds (AGL-3676): each post's card links its post and shows its cover by\n * the tokens the page fills per post, which a page's store admits whole only\n * where a listing places them.\n */\nexport function aiLayoutListingContext(\n context: AiDoctrineTreeContext,\n targets: AiLayoutTargets,\n): AiDoctrineTreeContext {\n const listsPosts = (targets.listings ?? []).some(\n (listing) => listing.kind === 'posts' && listing.placements.some((placement) => placement.screenId === targets.pageId),\n )\n if (!listsPosts) return context\n return { ...context, bindingTokens: [...new Set([...(context.bindingTokens ?? []), ...AI_LAYOUT_POST_ADDRESS_TOKENS])] }\n}\n\n/** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */\nexport function aiLayoutPagePrompt(input: {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n targets: AiLayoutTargets\n reusableComponents: boolean\n /** The sections a re-ask still needs, by plan index; absent on the first ask. */\n missing?: readonly number[]\n}): string {\n const { job, plan, screen, targets } = input\n const sections = screen.sections.map((section, index) => {\n const places = section.uses.length\n ? `; places ${section.uses.join(', ')}`\n : ''\n // A section the site's records fill (AGL-3676): the platform places them,\n // so the design writes the words around them and no group for them.\n const listed = aiLayoutListingAt(targets.listings ?? [], targets.pageId, index)\n if (listed) {\n return `${index + 1}. \"${section.name}\"${places}; the platform lists ${listed.listing.name}'s ${listed.listing.kind} here itself, with their photos and links: write only its heading and a line about them, and no cards, list or images for them`\n }\n const items = section.items ? `; shows ${section.items} items` : ''\n return `${index + 1}. \"${section.name}\"${places}${items}`\n })\n // A page merged into the blog is not offered: the blog is, by its own entry.\n const pages = targets.pages.filter((page) => !page.standsInFor).map((page) => `${page.label} (page:${page.id})`)\n const components = targets.components.map(\n (component) =>\n `${component.name} (${component.id}; props ${Object.keys(component.props).join(', ') || 'none'})`,\n )\n return [\n `Page: \"${screen.title}\" at ${screen.slug}`,\n aiJobBriefLine(job),\n // The kind of site the person picked sets how its pages are arranged (AGL-3660).\n ...aiSiteKindDesignLines(job.inputs),\n ...aiPlanReferenceLines(plan),\n `Sections to design, in order:`,\n ...sections,\n pages.length\n ? `Other pages of this site: ${pages.join(', ')}.`\n : 'This site has no other page to link.',\n targets.forms.length\n ? `The site's form: ${targets.forms.map((form) => `${form.name} (${form.id})`).join(', ')}.`\n : 'The site has no saved form: place none.',\n ...(components.length\n ? [`Components this page may place: ${components.join('; ')}.`]\n : []),\n input.reusableComponents\n ? 'Where a section repeats an item, place the component its plan line names with cards whose to is that component.'\n : 'This site keeps no reusable components: draw repeated items as cards, steps, stats or a list.',\n // A per-job word, so two identical briefs are designed apart.\n `Design seed: ${job.$id.slice(-6)}.`,\n ].join('\\n')\n}\n\n/**\n * The site a page is designed for (AGL-3660): the kind its job names, the\n * seed its look was drawn with — the same for every page of the site, so the\n * site reads as one design — and whether the page is its home. `null` for a\n * job that names no kind of site, which compiles the compiler's first way.\n */\nexport function aiLayoutDesignOf(job: Pick<AiJob, '$id' | 'inputs'>, slug: string): AiLayoutDesign | null {\n const kind = aiSiteKindOfInputs(job.inputs)\n if (!kind) return null\n const style = job.inputs?.['siteStyle'] as Record<string, unknown> | undefined\n const seed = typeof style?.['seed'] === 'number' && Number.isFinite(style['seed']) ? (style['seed'] as number) >>> 0 : aiSiteSeed(aiOriginJobId(job))\n return { kind: kind.id, seed, home: aiLayoutIsHomeSlug(slug) }\n}\n\n/** A validated language page, stored as the page keeps it. */\nexport interface AiLayoutPageBuilt {\n nodes: NodesMap\n load: AiLoadEstimate | null\n settled: AiLayoutSettlement[]\n /** What the last answer's gaps took out of the page (`ai-layout-gaps.ts`). */\n dropped: string[]\n /** How many repeated items each plan section shows, as built (AGL-3660). */\n items: number[]\n}\n\nexport interface AiLayoutPageCheckInput {\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n /**\n * What earlier answers gave, by plan index, shared between the page's\n * generations: a follow-up asking only for missing sections fills the rest.\n */\n kept?: Array<AiLayoutSection | null>\n /** The plan indices this answer's sections fill, in order; absent, every section. */\n only?: readonly number[]\n /** The site the page is drawn for (`aiLayoutDesignOf`); absent, the compiler's first design. */\n design?: AiLayoutDesign | null\n}\n\n/**\n * The follow-up turn for the sections a page's answer left out (AGL-3660):\n * the page as before, told which sections it already has, and asked for only\n * the missing ones, in order — the one part of a page asked for again.\n */\nexport function aiLayoutMissingSectionsPrompt(input: {\n base: string\n screen: AiBuildPlanScreen\n missing: readonly number[]\n}): string {\n const names = input.missing.map((index) => `${index + 1}. \"${input.screen.sections[index].name}\"`)\n return [\n input.base,\n '',\n `The other sections of this page are already designed. Design ONLY these, in this order, one entry in sections each: ${names.join('; ')}. Give each at least one block.`,\n ].join('\\n')\n}\n\n/** The plan indices a page still lacks, after the answers so far. */\nexport function aiLayoutMissingSections(kept: ReadonlyArray<AiLayoutSection | null>): number[] {\n return kept.flatMap((section, index) => (section ? [] : [index]))\n}\n\n/** The violation a planned section the answer gave nothing usable for is named by. */\nexport const AI_LAYOUT_SECTION_MISSING_CODE = 'layout-section-missing'\n\n/** The code a section planned with items that shows none is refused under (AGL-3660). */\nexport const AI_LAYOUT_SECTION_EMPTY_CODE = 'layout-section-no-items'\n\n/** How many of a section's planned items it must show: one for a short list, two for anything longer. */\nconst leastItems = (planned: number) => (planned >= 3 ? 2 : planned >= 1 ? 1 : 0)\n\n/**\n * How many repeated items each section shows once stored (AGL-3660): the\n * elements the compiler drew for them, under the ids they are stored by,\n * that are still in the page after anything later took words out.\n */\nexport function aiLayoutShownItems(\n itemIds: readonly (readonly string[])[],\n storedIds: Readonly<Record<string, string>>,\n nodes: Readonly<Record<string, unknown>>,\n): number[] {\n return itemIds.map((ids) => ids.filter((id) => storedIds[id] !== undefined && storedIds[id] in nodes).length)\n}\n\n/**\n * The sections a page planned with repeated items — cards, steps, a list —\n * that show none, or too few, once the page is built (AGL-3660). A heading\n * and an intro over nothing is a section a visitor reads as broken, so it is\n * asked for again.\n */\nexport function aiLayoutEmptyItemSections(\n screen: Pick<AiBuildPlanScreen, 'sections'>,\n items: readonly number[],\n /** Sections whose only items were customer quotes, which no answer may give (AGL-3676). */\n quotesOnly: readonly number[] = [],\n): AiDoctrineViolation[] {\n return screen.sections.flatMap((section, index): AiDoctrineViolation[] => {\n if (quotesOnly.includes(index)) return []\n const least = leastItems(section.items)\n if (!least) return []\n const shown = items[index] ?? 0\n if (shown >= least) return []\n return [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_EMPTY_CODE,\n message: `Section ${index + 1} (\"${section.name}\") is planned with ${section.items} items and shows ${shown || 'none'}.`,\n detail: `Give section ${index + 1} one cards, steps, stats, list or faq block holding its ${section.items} items, each with a title and its words.`,\n },\n ]\n })\n}\n\n/**\n * The check a language page answer is held to: each planned section read,\n * the page compiled, stored and checked whole. Sections that read are kept\n * across the re-ask, so the second answer is asked only for what was missing,\n * and a section it sends again that already read is not taken twice.\n */\nexport function aiLayoutPageCheck(\n input: AiLayoutPageCheckInput,\n): AiGenerationCheck<AiLayoutPageBuilt> {\n const kept: Array<AiLayoutSection | null> = input.kept ?? input.screen.sections.map(() => null)\n const fills = input.only ?? input.screen.sections.map((_, index) => index)\n const context = aiLayoutListingContext(input.context, input.targets)\n let answers = 0\n return (answer) => {\n // The last answer a generation takes has its gaps taken out rather than asked about again.\n answers += 1\n const last = answers >= AI_GENERATION_MAX_ATTEMPTS\n const reading = aiReadLayoutPage(\n answer,\n fills.length,\n fills.map((index) => input.screen.sections[index]),\n )\n reading.sections.forEach((section, position) => {\n // A section the answer gives is taken as given; one it leaves out keeps\n // what an earlier answer gave, so a re-ask may send only what was missing.\n if (section) kept[fills[position]] = section\n })\n const missing = kept.flatMap((section, index) => (section ? [] : [index]))\n if (missing.length) {\n const names = missing.map(\n (index) => `${index + 1} (\"${input.screen.sections[index].name}\")`,\n )\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: AI_LAYOUT_SECTION_MISSING_CODE,\n message: `The page left out section ${names.join(', ')}.`,\n detail: `Answer again with every section in order; the sections you already designed are kept, so only ${names.join(', ')} ${missing.length === 1 ? 'needs' : 'need'} its design. Give each at least one block.`,\n },\n ],\n }\n }\n const compiled = aiCompileLayoutPage(\n kept as AiLayoutSection[],\n { title: input.screen.title, sections: input.screen.sections },\n input.targets,\n {\n reusableComponents: input.reusableComponents,\n sectionIds: input.sectionIds,\n ...(input.design ? { design: input.design } : {}),\n },\n )\n const stored = aiLayoutStoredTree(\n compiled.tree,\n 'screen',\n context,\n input.sectionIds,\n )\n if (stored.ok === false) {\n // The compiler writes only palette trees, which the property spec holds;\n // a refusal here is the platform's, and is reported as one.\n console.error('ai layout page: a compiled page did not store', {\n error: stored.error,\n })\n return {\n value: null,\n violations: [\n {\n rule: null,\n code: 'layout-compile',\n message: 'This page could not be built. Try again.',\n detail: stored.error,\n },\n ],\n }\n }\n // No gap and no internal reference in the words of a page that is published as built.\n const copy = aiLayoutCopyCheck(\n stored.nodes,\n CANVAS_ROOT_ELEMENT_ID,\n {\n pages: input.targets.pages,\n ids: [\n ...input.targets.pages.map((page) => page.id),\n ...input.targets.forms.map((form) => form.id),\n ...input.targets.components.map((component) => component.id),\n ...input.sectionIds,\n ...(input.targets.pageId ? [input.targets.pageId] : []),\n ],\n },\n last,\n )\n const { nodes, dropped } = copy\n const report = validateAiDoctrineTree(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes },\n 'page',\n {\n ...context,\n scrollTargetIds: input.sectionIds,\n // The layout language draws its own picture cards (AGL-3660).\n repeatsCompiled: true,\n },\n )\n // Each section's items as they are stored, after anything the gaps took out.\n const items = aiLayoutShownItems(compiled.itemIds, stored.storedIds, nodes as unknown as Record<string, unknown>)\n // A street address or opening hours the job was never given is invented\n // (rule 14, AGL-3596), the same check a layout is held to; a phone number\n // or an email the compiler already writes as its gap.\n const violations: AiDoctrineViolation[] = [\n ...report.violations,\n ...copy.violations,\n ...aiLayoutEmptyItemSections(input.screen, items, compiled.quotesOnly),\n ...aiLayoutInventedContactViolations(\n { rootId: CANVAS_ROOT_ELEMENT_ID, nodes: nodes as unknown as Record<string, AiDoctrineNode> },\n input.targets.facts,\n ),\n ]\n return {\n value: violations.length\n ? null\n : {\n nodes,\n load: report.load,\n settled: [...reading.settled, ...compiled.settled],\n dropped,\n items,\n },\n violations,\n }\n }\n}\n\nexport interface AiLayoutPageRunInput {\n job: Pick<AiJob, 'brief' | 'inputs' | '$id'>\n plan: AiJobPlan\n screen: AiBuildPlanScreen\n sectionIds: readonly string[]\n targets: AiLayoutTargets\n context: AiDoctrineTreeContext\n reusableComponents: boolean\n inventory: AiSiteInventory | null\n model: string\n signal?: AbortSignal\n /** Wraps the check, for an eval that records each answer it reads. */\n observe?: (check: AiGenerationCheck<AiLayoutPageBuilt>) => AiGenerationCheck<AiLayoutPageBuilt>\n}\n\n/** Two generations' spend as one: every call is billed. */\nfunction spentTogether(first: AiGenerationSpend, second: AiGenerationSpend): AiGenerationSpend {\n const usage: AiUsage = {\n inputTokens: first.usage.inputTokens + second.usage.inputTokens,\n outputTokens: first.usage.outputTokens + second.usage.outputTokens,\n cacheReadTokens: first.usage.cacheReadTokens + second.usage.cacheReadTokens,\n cacheWriteTokens: first.usage.cacheWriteTokens + second.usage.cacheWriteTokens,\n }\n return {\n attempts: first.attempts + second.attempts,\n usage,\n estCostUsd: Math.round((first.estCostUsd + second.estCostUsd) * 1_000_000) / 1_000_000,\n model: second.model,\n stopReason: second.stopReason,\n effort: second.effort,\n }\n}\n\n/**\n * A whole page in the layout language: one answer for every section, and,\n * where that answer and its re-ask still left a section out, one follow-up\n * asking for only those (AGL-3660). The sections that read are kept between\n * them, so the page is compiled whole once every section is designed.\n */\nexport async function aiRunLayoutPage(input: AiLayoutPageRunInput): Promise<AiValidatedGeneration<AiLayoutPageBuilt>> {\n const kept: Array<AiLayoutSection | null> = input.screen.sections.map(() => null)\n const observe = input.observe ?? ((check) => check)\n const base = aiLayoutPagePrompt({\n job: input.job,\n plan: input.plan,\n screen: input.screen,\n targets: input.targets,\n reusableComponents: input.reusableComponents,\n })\n const ask = (content: string, maxTokens: number, only?: readonly number[]) =>\n runValidatedGeneration<AiLayoutPageBuilt>(AI_LAYOUT_PAGE_KIND, {\n step: 'job.page',\n model: input.model,\n instructions: AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS,\n inventory: input.inventory,\n messages: [{ role: 'user', content }],\n tool: AI_LAYOUT_PAGE_TOOL,\n maxTokens,\n cutOff: { noun: 'page', smaller: 'Write shorter copy, and fewer items in each group.' },\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: observe(\n aiLayoutPageCheck({\n screen: input.screen,\n sectionIds: input.sectionIds,\n targets: input.targets,\n context: input.context,\n reusableComponents: input.reusableComponents,\n kept,\n design: aiLayoutDesignOf(input.job, input.screen.slug),\n ...(only ? { only } : {}),\n }),\n ),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n const first = await ask(base, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model))\n const missing = aiLayoutMissingSections(kept)\n if (\n first.status !== 'needs_input' ||\n !missing.length ||\n !first.violations.every((violation) => violation.code === AI_LAYOUT_SECTION_MISSING_CODE)\n ) {\n return first\n }\n const second = await ask(\n aiLayoutMissingSectionsPrompt({ base, screen: input.screen, missing }),\n Math.min(AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS, AI_JOB_PAGE_LANGUAGE_BUDGET.maxTokens(input.model)),\n missing,\n )\n return { ...second, ...spentTogether(first, second) }\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","isAiPlanNewRef","aiHomeScreenIds","AI_LAYOUT_POST_ADDRESS_TOKENS","aiCompileLayoutPage","aiLayoutListingAt","aiLayoutListingsOf","AI_LAYOUT_LANGUAGE_TEXT","AI_LAYOUT_PAGE_TOOL","aiReadLayoutPage","aiLayoutCopyCheck","aiLayoutStoredTree","AI_GENERATION_MAX_ATTEMPTS","AI_STEP_TIERS","runValidatedGeneration","validateAiDoctrineTree","aiSiteKindDesignLines","aiSiteKindOfInputs","aiSiteSeed","aiOriginJobId","aiGenerationWorstCaseOnTierMs","aiJobStepBudget","aiJobBriefLine","aiPlanReferenceLines","aiLayoutIsHomeSlug","aiLayoutSiteAliases","aiLayoutSitePages","aiLayoutInventedContactViolations","AI_LAYOUT_LANGUAGE_INPUT","AI_LAYOUT_FORM_PAGE_INPUT","aiJobUsesLayoutLanguage","job","inputs","AI_LAYOUT_LANGUAGE_THINKING","thinking","effort","AI_LAYOUT_PAGE_THINKING_TOKENS","AI_LAYOUT_FRAME_THINKING_TOKENS","AI_JOB_PAGE_LANGUAGE_TOKENS","AI_LAYOUT_SECTION_MOST_TOKENS","AI_JOB_PAGE_LANGUAGE_FOLLOW_UP_TOKENS","AI_JOB_PAGE_LANGUAGE_BUDGET","tier","maxTokens","ownReadsMs","AI_LAYOUT_PAGE_KIND","AI_JOB_PAGE_LANGUAGE_INSTRUCTIONS","text","join","cacheBreakpoint","aiLayoutFormPageOfPlan","plan","formIds","created","Set","create","filter","entry","kind","map","name","toLowerCase","forms","screen","screens","find","sections","some","section","uses","ref","has","id","aiLayoutFacts","values","Object","value","brief","aiLayoutPageTargets","input","own","inventory","planned","pages","alias","page","row","template","label","slug","formPage","pageId","includes","homeIds","form","formPageId","components","component","props","facts","listings","aiLayoutListingContext","context","targets","listsPosts","listing","placements","placement","screenId","bindingTokens","aiLayoutPagePrompt","index","places","length","listed","items","standsInFor","keys","title","reusableComponents","$id","slice","aiLayoutDesignOf","style","seed","Number","isFinite","home","aiLayoutMissingSectionsPrompt","names","missing","base","aiLayoutMissingSections","kept","flatMap","AI_LAYOUT_SECTION_MISSING_CODE","AI_LAYOUT_SECTION_EMPTY_CODE","leastItems","aiLayoutShownItems","itemIds","storedIds","nodes","ids","undefined","aiLayoutEmptyItemSections","quotesOnly","least","shown","rule","code","message","detail","aiLayoutPageCheck","fills","only","_","answers","answer","last","reading","forEach","position","violations","compiled","sectionIds","design","stored","tree","ok","console","error","copy","dropped","report","rootId","scrollTargetIds","repeatsCompiled","load","settled","spentTogether","first","second","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","attempts","estCostUsd","Math","round","model","stopReason","aiRunLayoutPage","observe","check","ask","content","step","instructions","messages","role","tool","cutOff","noun","smaller","signal","status","every","violation","min"],"mappings":"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"}