@aglyn/plugins-ai 1.0.0-beta.231 → 1.0.0-beta.232

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 (220) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-follow.component.js +1 -0
  5. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  6. package/src/lib/components/ai-job-links.js +13 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  9. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  10. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  11. package/src/lib/components/ai-media-create.component.js +107 -55
  12. package/src/lib/components/ai-media-create.component.js.map +1 -1
  13. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  14. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  15. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  16. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  17. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  18. package/src/lib/components/ai-site-start-card.component.js +80 -77
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  21. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  22. package/src/lib/components/ai-usage-strip.component.js +1 -0
  23. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  24. package/src/lib/components/assist-panel.component.d.ts +10 -0
  25. package/src/lib/components/assist-panel.component.js +119 -9
  26. package/src/lib/components/assist-panel.component.js.map +1 -1
  27. package/src/lib/components/assist-signals-page.component.js +2 -0
  28. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  32. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  33. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  34. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  35. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  36. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  37. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  38. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  39. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  40. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  41. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  42. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  43. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  44. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  45. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  46. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  47. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  48. package/src/lib/jobs/ai-job-build-step.js +3 -1
  49. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  51. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  52. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  53. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  54. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  56. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  57. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  58. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  59. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  60. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  61. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  62. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  64. package/src/lib/jobs/ai-job-page-language.js +72 -9
  65. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  66. package/src/lib/jobs/ai-job-page-step.js +12 -2
  67. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
  69. package/src/lib/jobs/ai-job-plan-step.js +54 -20
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  72. package/src/lib/jobs/ai-job-site-content.js +443 -0
  73. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  74. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  75. package/src/lib/jobs/ai-job-site-look.js +322 -0
  76. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  77. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  78. package/src/lib/jobs/ai-job-site-step.js +163 -24
  79. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  80. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  81. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  82. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  83. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  84. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  85. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  86. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  87. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  89. package/src/lib/layout-language/ai-layout-language.js +92 -9
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  92. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  93. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  94. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  95. package/src/lib/layout-language/ai-layout-store.js +7 -1
  96. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  97. package/src/lib/model/ai-build-progress.js +4 -1
  98. package/src/lib/model/ai-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-edit-job.d.ts +109 -0
  100. package/src/lib/model/ai-edit-job.js +157 -0
  101. package/src/lib/model/ai-edit-job.js.map +1 -0
  102. package/src/lib/model/ai-job-activity.js +1 -0
  103. package/src/lib/model/ai-job-activity.js.map +1 -1
  104. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  105. package/src/lib/model/ai-jobs.types.js.map +1 -1
  106. package/src/lib/model/ai-logic-job.d.ts +18 -1
  107. package/src/lib/model/ai-logic-job.js +24 -3
  108. package/src/lib/model/ai-logic-job.js.map +1 -1
  109. package/src/lib/model/ai-media-credits.d.ts +8 -4
  110. package/src/lib/model/ai-media-credits.js +11 -4
  111. package/src/lib/model/ai-media-credits.js.map +1 -1
  112. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  113. package/src/lib/model/ai-media-kinds.js +98 -0
  114. package/src/lib/model/ai-media-kinds.js.map +1 -0
  115. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  116. package/src/lib/model/ai-site-build-progress.js +28 -11
  117. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  118. package/src/lib/model/ai-site-context.d.ts +111 -0
  119. package/src/lib/model/ai-site-context.js +222 -0
  120. package/src/lib/model/ai-site-context.js.map +1 -0
  121. package/src/lib/model/ai-site-job.d.ts +10 -2
  122. package/src/lib/model/ai-site-job.js +12 -5
  123. package/src/lib/model/ai-site-job.js.map +1 -1
  124. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  125. package/src/lib/model/ai-site-kinds.js +1942 -0
  126. package/src/lib/model/ai-site-kinds.js.map +1 -0
  127. package/src/lib/model/ai-site-look.d.ts +150 -0
  128. package/src/lib/model/ai-site-look.js +1236 -0
  129. package/src/lib/model/ai-site-look.js.map +1 -0
  130. package/src/lib/model/ai-site-memory.d.ts +80 -0
  131. package/src/lib/model/ai-site-memory.js +171 -0
  132. package/src/lib/model/ai-site-memory.js.map +1 -0
  133. package/src/lib/model/ai-site-start.d.ts +10 -1
  134. package/src/lib/model/ai-site-start.js +16 -2
  135. package/src/lib/model/ai-site-start.js.map +1 -1
  136. package/src/lib/model/assist-follow-up.d.ts +92 -0
  137. package/src/lib/model/assist-follow-up.js +128 -0
  138. package/src/lib/model/assist-follow-up.js.map +1 -0
  139. package/src/lib/plugin-config.js +2 -0
  140. package/src/lib/plugin-config.js.map +1 -1
  141. package/src/lib/plugin.js +20 -1
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.d.ts +9 -1
  144. package/src/lib/providers/anthropic.js +143 -9
  145. package/src/lib/providers/anthropic.js.map +1 -1
  146. package/src/lib/providers/catalog.d.ts +37 -7
  147. package/src/lib/providers/catalog.js +103 -11
  148. package/src/lib/providers/catalog.js.map +1 -1
  149. package/src/lib/providers/contract.d.ts +29 -0
  150. package/src/lib/providers/contract.js.map +1 -1
  151. package/src/lib/providers/image-contract.d.ts +22 -1
  152. package/src/lib/providers/image-contract.js +40 -1
  153. package/src/lib/providers/image-contract.js.map +1 -1
  154. package/src/lib/providers/image-endpoints.d.ts +24 -0
  155. package/src/lib/providers/image-endpoints.js +28 -0
  156. package/src/lib/providers/image-endpoints.js.map +1 -0
  157. package/src/lib/providers/model-choice.js +23 -2
  158. package/src/lib/providers/model-choice.js.map +1 -1
  159. package/src/lib/providers/routing.js +18 -0
  160. package/src/lib/providers/routing.js.map +1 -1
  161. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  162. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  163. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  164. package/src/lib/providers/vertex-image.d.ts +13 -27
  165. package/src/lib/providers/vertex-image.js +15 -42
  166. package/src/lib/providers/vertex-image.js.map +1 -1
  167. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  168. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  169. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  170. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  171. package/src/lib/runtime/ai-dev-env.js +72 -0
  172. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  173. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  174. package/src/lib/runtime/ai-dev-replay.js +313 -0
  175. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  176. package/src/lib/runtime/ai-doctrine.js +5 -1
  177. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  178. package/src/lib/runtime/ai-eval-live.js +4 -0
  179. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  180. package/src/lib/runtime/ai-eval.d.ts +13 -1
  181. package/src/lib/runtime/ai-eval.js +84 -1
  182. package/src/lib/runtime/ai-eval.js.map +1 -1
  183. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  184. package/src/lib/runtime/ai-live-batch.js +89 -0
  185. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  186. package/src/lib/runtime/ai-runtime.js +46 -2
  187. package/src/lib/runtime/ai-runtime.js.map +1 -1
  188. package/src/lib/runtime/site-context.d.ts +58 -0
  189. package/src/lib/runtime/site-context.js +127 -0
  190. package/src/lib/runtime/site-context.js.map +1 -0
  191. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  192. package/src/lib/server/ai-admin-conversations.js +197 -0
  193. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  194. package/src/lib/server/ai-media-image.d.ts +5 -5
  195. package/src/lib/server/ai-media-image.js +25 -11
  196. package/src/lib/server/ai-media-image.js.map +1 -1
  197. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  198. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  199. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  200. package/src/lib/server/assist-chat.d.ts +11 -0
  201. package/src/lib/server/assist-chat.js +57 -18
  202. package/src/lib/server/assist-chat.js.map +1 -1
  203. package/src/lib/server/assist-edit-applied.js +16 -3
  204. package/src/lib/server/assist-edit-applied.js.map +1 -1
  205. package/src/lib/server/assist-view-context.d.ts +19 -1
  206. package/src/lib/server/assist-view-context.js +38 -1
  207. package/src/lib/server/assist-view-context.js.map +1 -1
  208. package/src/lib/server.js +10 -0
  209. package/src/lib/server.js.map +1 -1
  210. package/src/lib/subprocessors.js +17 -1
  211. package/src/lib/subprocessors.js.map +1 -1
  212. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  213. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  214. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  215. package/src/lib/usage/assist-usage.d.ts +3 -0
  216. package/src/lib/usage/assist-usage.js +4 -2
  217. package/src/lib/usage/assist-usage.js.map +1 -1
  218. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  219. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  220. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport {\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n}\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n return {\n props: { color: 'inherit' },\n sx: {\n bgcolor: 'background.paper',\n boxShadow: 'none',\n borderBottom: 1,\n borderColor: 'divider',\n },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true },\n settled: [],\n scrollTo: {},\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n screenId: entry.id,\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n for (const block of header.blocks) {\n if (block !== ctaBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : ''\n const ctaTo =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n const ctaProps = ctaTo\n ? {\n children: ctaLabel,\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const band = footer?.band ?? 'soft'\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n const top =\n filled.length > 1\n ? compileRow(scope, filled)\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","AI_LAYOUT_MAX_ITEMS","aiLayoutResolveLink","AiLayoutTreeBuilder","headerLook","band","props","color","sx","boxShadow","bgcolor","borderBottom","borderColor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","title","siteName","pageId","options","reusableComponents","settled","scrollTo","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","name","blocks","look","scope","navLink","entry","role","interactions","id","add","children","label","slice","screenId","renderAs","textDecoration","nodes","pages","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","map","ctaBlock","find","block","kind","push","what","ctaLabel","ctaTo","to","ctaProps","cta","sm","flexShrink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","slot","footer","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","navItems","linksPages","flatMap","given","notice","item","items","some","has","pageLinks","cols","columns","col","undefined","Math","min","weights","filled","weight","top","copyright","bottom","pt","borderTop","stack","container","py","element","colorScheme"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport {\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n}\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n screenId: entry.id,\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n for (const block of header.blocks) {\n if (block !== ctaBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : ''\n const ctaTo =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n const ctaProps = ctaTo\n ? {\n children: ctaLabel,\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const band = footer?.band ?? 'soft'\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n const top =\n filled.length > 1\n ? compileRow(scope, filled)\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","AI_LAYOUT_MAX_ITEMS","aiLayoutResolveLink","AiLayoutTreeBuilder","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","title","siteName","pageId","options","reusableComponents","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","name","blocks","look","scope","navLink","entry","role","interactions","id","add","children","label","slice","screenId","renderAs","textDecoration","nodes","pages","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","map","ctaBlock","find","block","kind","push","what","ctaLabel","ctaTo","to","ctaProps","cta","sm","flexShrink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","slot","footer","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","navItems","linksPages","flatMap","given","notice","item","items","some","has","pageLinks","cols","columns","col","undefined","Math","min","weights","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","py","element","colorScheme"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,eAAe,QAAQ,gCAA4B;AAC5D,SACEC,oBAAoB,EACpBC,MAAM,EACNC,WAAW,EACXC,UAAU,EACVC,gBAAgB,QAGX,0BAAsB;AAC7B,SAASC,eAAe,QAAQ,sBAAkB;AAClD,SACEC,mBAAmB,QAKd,0BAAsB;AAC7B,SACEC,mBAAmB,QAGd,uBAAmB;AAC1B,SAASC,mBAAmB,QAA8B,sBAAkB;AA+B5E,mDAAmD,GACnD,SAASC,WAAWC,IAAkB;IAIpC,IAAIA,SAAS,SACX,OAAO;QAAEC,OAAO;YAAEC,OAAO;QAAU;QAAGC,IAAI;YAAEC,WAAW;QAAO;IAAE;IAClE,IAAIJ,SAAS,QAAQ;QACnB,OAAO;YACLC,OAAO;gBAAEC,OAAO;YAAU;YAC1BC,IAAI;gBACFC,WAAW;gBACXC,SAAS;gBACTH,OAAO;YACT;QACF;IACF;IACA,uEAAuE;IACvE,2DAA2D;IAC3D,OAAO;QACLD,OAAO;YAAEC,OAAO;QAAU;QAC1BC,IAAI;YAAEE,SAAS;QAAmB;IACpC;AACF;AAEA,+FAA+F,GAC/F,OAAO,SAASC,oBAAoBC,IAAa;IAC/C,OAAO,OAAOA,SAAS,YAAY,mDAAmDC,IAAI,CAACD;AAC7F;AAEA,8FAA8F,GAC9F,MAAME,oBAAwD,IAAIC,IAAI;IACpE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,sFAAsF,GACtF,SAASC,WACPC,IAAe,EACfC,EAAU,EACVb,IAAkB,EAClBc,QAAiB;IAEjB,OAAO;QACLF;QACAG,OAAOF,OAAO,WAAW,IAAI;QAC7BA;QACAb;QACAc;QACAE,OAAO;QACPC,QAAQ;QACRC,WAAWtB;QACXuB,OAAO;QACPC,MAAM;YAAEC,UAAU,EAAE;YAAEC,MAAM,CAAC;YAAGC,aAAa;QAAK;IACpD;AACF;AAEA,2FAA2F,GAC3F,OAAO,SAASC,qBACdL,KAAyE,EACzEM,IAAuB,EACvBC,OAAwB;QAqBTP,eACFQ;IApBb,MAAMC,OAAO,IAAI9B,oBAAoB;IACrC,MAAMc,OAAkB;QACtBgB;QACAH,MAAM;YAAEI,OAAOJ,KAAKK,QAAQ;YAAET,UAAU,EAAE;QAAC;QAC3CK,SAAS,aAAKA;YAASK,QAAQ;;QAC/B,0FAA0F;QAC1FC,SAAS;YAAEC,oBAAoB;QAAK;QACpCC,SAAS,EAAE;QACXC,UAAU,CAAC;QACX,6DAA6D;QAC7DC,SAAS,EAAE;QACX,mFAAmF;QACnFC,QAAQ/C;QACRgD,QAAQ;YAAEC,OAAO;YAAGC,MAAM;QAAE;QAC5BjB,aAAa;QACbkB,UAAUpD,gBAAgBqD,OAAO;QACjCC,aAAa,IAAIjC;IACnB;IACA,MAAMkC,OAAOjD,gBAAgB8B,KAAKK,QAAQ,EAAE,cAAc;IAC1D,MAAMH,UAASR,gBAAAA,MAAMQ,MAAM,YAAZR,gBAAgB;QAAE0B,QAAQ,EAAE;IAAC;IAC5C,MAAM7C,QAAO2B,eAAAA,OAAO3B,IAAI,YAAX2B,eAAe;IAC5B,MAAMmB,OAAO/C,WAAWC;IACxB,MAAM+C,QAAQpC,WACZC,MACA,UACAZ,SAAS,WAAWA,SAAS,SAAS,UAAU,SAChD;IAEF,MAAMgD,UAAU,CACdC,OACAC,MACAC;QAEA,MAAMC,KAAKxB,KAAKyB,GAAG,CACjB,iBACA;YACEC,UACE3D,gBAAgBsD,MAAMM,KAAK,EAAE,YAAYN,MAAMM,KAAK,CAACC,KAAK,CAAC,GAAG;YAChEC,UAAUR,MAAMG,EAAE;YAClBM,UAAU;YACVxD,OAAO;QACT,GACA;YAAEyD,gBAAgB;QAAO,GACzB,MACAT;QAEF,IAAIC,cAAcvB,KAAKgC,KAAK,CAACR,GAAG,CAACD,YAAY,GAAGA;QAChD,OAAOC;IACT;IACA,MAAMS,QAAQpC,KAAKqC,QAAQ,CAACC,MAAM,CAAC,CAACd,QAAUA,MAAMM,KAAK,CAACS,IAAI;IAC9D,MAAMC,cAActC,OAAOuC,KAAK,KAAK,YAAYL,MAAMM,MAAM,GAAG;IAChE,yEAAyE;IACzE,+EAA+E;IAC/E,MAAMC,UAAU;QACdC,YAAY;QACZC,YAAY;QACZX,gBAAgB;QAChBY,YAAY;QACZC,UAAU;QACVC,cAAc;QACdC,UAAU;OACNT,cAAc,CAAC,IAAI;QAAEU,UAAU;IAAE;IAEvC,MAAMpC,QAAQd,KAAKmD,MAAM,GACrBhD,KAAKyB,GAAG,CACN,iBACA;QACEC,UAAUV,KAAKY,KAAK,CAAC,GAAG;QACxBC,UAAUhC,KAAKmD,MAAM;QACrBlB,UAAU;QACVxD,OAAO;IACT,GACAkE,SACA,MACA,WAEFxC,KAAKyB,GAAG,CACN,iBACA;QAAEC,UAAUV;QAAMiC,SAAS;QAAMC,WAAW;IAAI,GAChDV,SACA,MACA;IAEN,MAAMW,MAAMlB,MAAMM,MAAM,GACpBvC,KAAKyB,GAAG,CACN,YACA;QACEyB,WAAW;QACXE,WAAW;QACXC,WAAW;QACXC,SAAS;QACTC,YAAY;IACd,GACA;QAAEC,SAAS;YAAEC,IAAI;YAAQC,IAAI;QAAO;IAAE,GACtCzB,MAAM0B,GAAG,CAAC,CAACtC,QAAUD,QAAQC,OAAO,aACpC,SAEF;IACJ,0EAA0E;IAC1E,MAAMuC,WAAW7D,OAAOkB,MAAM,CAAC4C,IAAI,CAAC,CAACC,QAAUA,MAAMC,IAAI,KAAK;IAC9D,KAAK,MAAMD,SAAS/D,OAAOkB,MAAM,CAAE;QACjC,IAAI6C,UAAUF,UACZ5E,KAAKsB,OAAO,CAAC0D,IAAI,CAAC;YAChB/E,IAAI;YACJgF,MAAM,CAAC,EAAE,EAAEH,MAAMC,IAAI,CAAC,wBAAwB,CAAC;QACjD;IACJ;IACA,MAAMG,WAAWN,WAAW7F,gBAAgB6F,SAASjF,IAAI,EAAE,WAAW;IACtE,MAAMwF,QACJP,YAAYM,WACRjG,oBAAoB2F,SAASQ,EAAE,EAAEF,UAAU/C,MAAM3B,IAAI,EAAER,KAAKc,OAAO,IACnE;IACN,IAAI8D,YAAY,CAACO,OACfnF,KAAKsB,OAAO,CAAC0D,IAAI,CAAC;QAChB/E,IAAI;QACJgF,MAAM,CAAC,YAAY,EAAEC,SAAS,6BAA6B,CAAC;IAC9D;IACF,MAAMG,WAAWF,QACb;QACEzC,UAAUwC;QACVjB,SAAS;QACT3E,OAAO6C,MAAM/C,IAAI,KAAK,UAAU,cAAc;OAC3CN,iBAAiBqG,UAEtB;IACJ,iEAAiE;IACjE,MAAMG,MAAMD,WACRrE,KAAKyB,GAAG,CACN,aACA4C,UACA;QACEb,SAAS;YAAEC,IAAI;YAAQc,IAAI;QAAc;QACzC5B,YAAY;QACZ6B,YAAY;IACd,GACA,MACA,SAEF;IACJ,2EAA2E;IAC3E,8BAA8B;IAC9B,MAAMC,QAAQ;QAACC;KAAuB;IACtC,MAAMC,SAAS1C,MAAMM,MAAM,GACvBvC,KAAKyB,GAAG,CACN,aACA;QAAEmD,QAAQ;IAAQ,GAClB,MACA;QACE5E,KAAKyB,GAAG,CACN,YACA;YAAEyB,WAAW;YAAOE,WAAW;QAAO,GACtC;YACEI,SAAS;YACTqB,eAAe;YACfC,KAAK;YACLC,GAAG;YACHtC,YAAY;QACd,GACA;eACKR,MAAM0B,GAAG,CAAC,CAACtC,QAAUD,QAAQC,OAAO,YAAYoD;YACnDJ,WACIrE,KAAKyB,GAAG,CACN,aACA,aAAK4C;gBAAUW,WAAW;gBAC1B;gBAAEC,IAAI;YAAE,GACR,MACA,aAEF;SACL,EACD;KAEH,EACD,YAEF;IACJ,MAAMC,SAASP,SACX3E,KAAKyB,GAAG,CACN,mBACA;QAAE2B,WAAW;IAAgB,GAC7B;QAAEI,SAAS;YAAEC,IAAI;YAAeC,IAAI;QAAO;QAAGc,YAAY;IAAE,GAC5D,MACA,gBAEF;IACJ,MAAMW,SACJhC,OAAOd,cACHrC,KAAKyB,GAAG,CACN,UACA,MACA;QAAEsB,UAAU;QAAGS,SAAS;QAAQ4B,gBAAgB;IAAS,GACzD;QAACjC;KAAI,EACL,aAEFA;IACN,6FAA6F;IAC7F,MAAMkC,MAAMrF,KAAKyB,GAAG,CAClB,gBACA;QAAE6D,UAAU;IAAK,GACjB;QAAE9B,SAAS;QAAQD,YAAY;QAAUgC,WAAW;IAAE,GACtD;QAAC5E;QAAOwE;QAAQb;QAAKY;QAAQP;KAAO,EACpC;IAEF,MAAMa,UAAUxF,KAAKyB,GAAG,CACtB,cACA;QAAEgE,gBAAgB;IAAK,GACvB;QAAEC,WAAW;YAAEjC,IAAI;YAAIC,IAAI;QAAG;IAAE,GAChC;QAAC2B;KAAI,EACL;IAEF,MAAMM,MAAM3F,KAAKyB,GAAG,CAClB,aACA,aACKP,KAAK7C,KAAK;QACbuH,UAAU;QACV1C,WAAW;QACXE,WAAW;QAEblC,KAAK3C,EAAE,EACP;QAACiH;KAAQ,EACT;IAEF,wEAAwE;IACxE,4BAA4B;IAC5B,MAAMK,OAAO7F,KAAKyB,GAAG,CACnB,cACA;QAAEyB,WAAW;IAAO,GACpB;QAAEH,UAAU;IAAE,GACd,MACA;IAEF,MAAM+C,SAASC,cAAc/G,MAAMO,MAAMuG,MAAM,EAAEjG,MAAMmB;IACvD,MAAMgF,SAAShG,KAAKyB,GAAG,CACrB,YACA,MACA;QAAEiE,WAAW;IAAQ,GACrB;QAACC;QAAKE;QAAMC;KAAO,EACnB;IAEF9F,KAAKiG,KAAK,CAACD,QAAQ,IAAIlH,IAAI;QAAC+G;KAAK;IACjC,MAAMK,SAASlG,KAAKyB,GAAG,CAAC,OAAO,MAAM,MAAM;QAACuE;KAAO,EAAE;IACrD,OAAO;QAAEhG,MAAM;YAAEkG;YAAQlE,OAAOhC,KAAKgC,KAAK;QAAC;QAAG1B,SAAStB,KAAKsB,OAAO;IAAC;AACtE;AAEA,mDAAmD,GACnD,MAAMoE,yBAAyB;IAC7BlD,IAAI;IACJR,MAAM;IACNmF,SAAS;IACTC,SAAS;QAAEC,OAAO;QAAgBC,WAAW;IAAK;IAClDC,OAAO;QAAC;YAAEC,MAAM;QAAc;KAAE;AAClC;AAEA;;;;CAIC,GACD,SAAST,cACP/G,IAAe,EACf8G,MAA8B,EAC9BjG,IAAuB,EACvBmB,IAAY;;IAEZ,MAAMhB,OAAOhB,KAAKgB,IAAI;IACtB,MAAM5B,eAAO0H,0BAAAA,OAAQ1H,IAAI,mBAAI;IAC7B,MAAM+C,QAAQpC,WAAWC,MAAM,UAAUZ,MAAM0H,CAAAA,0BAAAA,OAAQxD,KAAK,MAAK;IACjE,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,yCAAyC;IACzC,MAAMmE,WAAW5G,KAAKqC,QAAQ,CAACyB,GAAG,CAAC,CAACtC,QAAW,CAAA;YAC7CpB,OAAOoB,MAAMM,KAAK;YAClBhD,MAAM;YACNyF,IAAI,CAAC,KAAK,EAAE/C,MAAMG,EAAE,EAAE;QACxB,CAAA;IACA,IAAIkF,aAAa;IACjB,MAAMzF,SAAS,UAAC6E,0BAAAA,OAAQ7E,MAAM,oBAAI,EAAE,EAAE0F,OAAO,CAAC,CAACC;YAmB1C9C;YAXW8C;QAPd,uEAAuE;QACvE,uDAAuD;QACvD,IAAIlI,oBAAoBkI,MAAMjI,IAAI,GAAG;YACnCK,KAAKsB,OAAO,CAAC0D,IAAI,CAAC;gBAAE/E,IAAI;gBAAUgF,MAAM;YAA8C;YACtF,OAAO,EAAE;QACX;QACA,MAAM4C,SAAS,CAACC,OAA0CpI,oBAAoBoI,KAAK7G,KAAK,KAAKvB,oBAAoBoI,KAAKnI,IAAI;QAC1H,MAAMmF,QAAQ8C,EAAAA,eAAAA,MAAMG,KAAK,qBAAXH,aAAaI,IAAI,CAACH,WAAU,aAAKD;YAAOG,OAAOH,MAAMG,KAAK,CAAC5E,MAAM,CAAC,CAAC2E,OAAS,CAACD,OAAOC;aAAWF;QAC7G,IAAI9C,UAAU8C,OAAO5H,KAAKsB,OAAO,CAAC0D,IAAI,CAAC;YAAE/E,IAAI;YAAUgF,MAAM;QAAwD;QACrH,IAAIpF,kBAAkBoI,GAAG,CAACnD,MAAMC,IAAI,GAAG;YACrC/E,KAAKsB,OAAO,CAAC0D,IAAI,CAAC;gBAChB/E,IAAI;gBACJgF,MAAM,CAAC,EAAE,EAAEH,MAAMC,IAAI,CAAC,wBAAwB,CAAC;YACjD;YACA,OAAO,EAAE;QACX;QACA,MAAMmD,YACJpD,MAAMC,IAAI,KAAK,UACf,EAACD,eAAAA,MAAMiD,KAAK,YAAXjD,eAAe,EAAE,EAAEkD,IAAI,CAAC,CAACF;gBAAuBA;mBAAd,SAASlI,IAAI,EAACkI,WAAAA,KAAK1C,EAAE,YAAP0C,WAAW;;QAC9D,IAAI,CAACI,WAAW,OAAO;YAACpD;SAAM;QAC9B,IAAI4C,cAAc,CAACD,SAASlE,MAAM,EAAE;YAClCvD,KAAKsB,OAAO,CAAC0D,IAAI,CAAC;gBAAE/E,IAAI;gBAAUgF,MAAM;YAAwC;YAChF,OAAO,EAAE;QACX;QACAyC,aAAa;QACb,OAAO;YAAC,aAAK5C;gBAAOiD,OAAON;;SAAW;IACxC;IACA,MAAMU,OAAOrB,CAAAA,0BAAAA,OAAQqB,IAAI,KAAIrB,OAAOqB,IAAI,CAAC5E,MAAM,IAAI,IAAIuD,OAAOqB,IAAI,GAAG;IACrE,MAAMC,UAA6BD,OAAOA,KAAKxD,GAAG,CAAC,IAAM,EAAE,IAAI;QAAC,EAAE;KAAC;IACnE,KAAK,MAAMG,SAAS7C,OAClBmG,OAAO,CACLD,QAAQrD,MAAMuD,GAAG,KAAKC,YAAYC,KAAKC,GAAG,CAAC1D,MAAMuD,GAAG,EAAEF,KAAK5E,MAAM,GAAG,KAAK,EAC1E,CAACyB,IAAI,CAACF;IACT,MAAM2D,UAAUN,OAAO;WAAIA;KAAK,GAAG;QAAC;KAAE;IACtC,IAAI,CAACT,cAAc7G,KAAKqC,QAAQ,CAACK,MAAM,EAAE;QACvC6E,QAAQpD,IAAI,CAAC;YACX;gBACED,MAAM;gBACNgD,OAAOlH,KAAKqC,QAAQ,CAACyB,GAAG,CAAC,CAACtC,QAAW,CAAA;wBACnCpB,OAAOoB,MAAMM,KAAK;wBAClBhD,MAAM;wBACNyF,IAAI,CAAC,KAAK,EAAE/C,MAAMG,EAAE,EAAE;oBACxB,CAAA;YACF;SACD;QACDiG,QAAQzD,IAAI,CAAC;IACf;IACA,MAAM0D,SAASN,QACZzD,GAAG,CAAC,CAACqC,QAAQJ;YAAgC6B;eAAlB;YAAEzB;YAAQ2B,MAAM,GAAEF,oBAAAA,OAAO,CAAC7B,SAAS,YAAjB6B,oBAAqB;QAAE;OACpEtF,MAAM,CAAC,CAACd,QAAUA,MAAM2E,MAAM,CAACzD,MAAM;IACxC,MAAMqF,MACJF,OAAOnF,MAAM,GAAG,IACZ1E,WAAWsD,OAAOuG,UAClBA,OAAOnF,MAAM,KAAK,IAChB3E,YAAYuD,OAAOuG,MAAM,CAAC,EAAE,CAAC1B,MAAM,EAAE,UACrC;IACR,MAAM6B,YAAY7H,KAAKyB,GAAG,CACxB,iBACA;QAAEC,UAAU,CAAC,EAAE,EAAEV,MAAM;QAAEiC,SAAS;QAASC,WAAW;IAAI,GAC1D/B,MAAM/C,IAAI,KAAK,UAAU,OAAO;QAAEE,OAAO;IAAiB,GAC1D,MACA;IAEF,MAAMwJ,SAAS9H,KAAKyB,GAAG,CACrB,UACA,MACA;QAAEsG,IAAI;QAAGC,WAAW;QAAGC,aAAa;IAAU,GAC9C;QAACJ;KAAU,EACX;IAEF,MAAMK,QAAQlI,KAAKyB,GAAG,CACpB,YACA;QAAE6B,SAAS;IAAI,GACf,MACA;QAACsE;QAAKE;KAAO,EACb;IAEF,MAAMK,YAAYnI,KAAKyB,GAAG,CACxB,gBACA;QAAE6D,UAAU;IAAK,GACjB;QAAE8C,IAAI;YAAE3E,IAAI;YAAGC,IAAI;QAAE;IAAE,GACvB;QAACwE;KAAM,EACP;IAEF,OAAOlI,KAAKyB,GAAG,CACb,WACA;QACE4G,SAAS;QACTjF,WAAW;OACPjC,MAAM/C,IAAI,KAAK,SAAS;QAAEkK,aAAa;IAAO,IAAI,CAAC,IAEzDnH,MAAM/C,IAAI,KAAK,SACX;QAAEK,SAAS;QAAoBuJ,WAAW;QAAGC,aAAa;IAAU,IACpEtK,OAAOwD,MAAM/C,IAAI,GACrB;QAAC+J;KAAU,EACX;AAEJ"}
@@ -146,13 +146,20 @@ export interface AiLayoutPageReading {
146
146
  sections: Array<AiLayoutSection | null>;
147
147
  settled: AiLayoutSettlement[];
148
148
  }
149
+ /** What a planned section is, as the reader matches an answer's sections to it. */
150
+ export interface AiLayoutPlannedSection {
151
+ name: string;
152
+ items: number;
153
+ }
149
154
  /**
150
- * The page an answer describes, against the number of sections its plan
151
- * names: each planned section takes the answer's section at its position,
152
- * an answer's extra sections are left out, and a planned section the answer
153
- * left empty or out is `null` — the one thing a re-ask asks for again.
155
+ * The page an answer describes, against the sections its plan names: each
156
+ * planned section takes the answer's section at its position — or, where the
157
+ * answer gave more sections than the plan names and the plan's sections are
158
+ * given, the one that fits it best (`alignSections`) — an answer's extra
159
+ * sections are left out, and a planned section the answer left empty or out
160
+ * is `null` — the one thing a re-ask asks for again.
154
161
  */
155
- export declare function aiReadLayoutPage(raw: unknown, planned: number): AiLayoutPageReading;
162
+ export declare function aiReadLayoutPage(raw: unknown, planned: number, plan?: readonly AiLayoutPlannedSection[]): AiLayoutPageReading;
156
163
  /** A frame answer read: a header and a footer, either `null` where it was not usable. */
157
164
  export declare function aiReadLayoutFrame(raw: unknown): {
158
165
  header: AiLayoutSection | null;
@@ -395,23 +395,106 @@ function readBlock(raw, at, settled) {
395
395
  });
396
396
  return section.blocks.length ? section : null;
397
397
  }
398
+ const WORD = /[a-z0-9]+/g;
399
+ const STOP = new Set([
400
+ 'a',
401
+ 'an',
402
+ 'and',
403
+ 'the',
404
+ 'of',
405
+ 'for',
406
+ 'to',
407
+ 'our',
408
+ 'your',
409
+ 'with',
410
+ 'in',
411
+ 'on',
412
+ 'we',
413
+ 'us',
414
+ 'you'
415
+ ]);
416
+ const wordsOf = (text)=>{
417
+ var _text_toLowerCase_match;
418
+ return new Set(((_text_toLowerCase_match = text.toLowerCase().match(WORD)) != null ? _text_toLowerCase_match : []).filter((word)=>!STOP.has(word)));
419
+ };
420
+ /** The kinds that show a section's repeated items. */ const ITEM_KINDS = new Set([
421
+ 'list',
422
+ 'cards',
423
+ 'steps',
424
+ 'stats',
425
+ 'quotes',
426
+ 'faq',
427
+ 'component'
428
+ ]);
398
429
  /**
399
- * The page an answer describes, against the number of sections its plan
400
- * names: each planned section takes the answer's section at its position,
401
- * an answer's extra sections are left out, and a planned section the answer
402
- * left empty or out is `null` — the one thing a re-ask asks for again.
403
- */ export function aiReadLayoutPage(raw, planned) {
430
+ * How well an answer's section fits a planned one: the words its headings
431
+ * share with the plan's name, and whether it shows items where the plan
432
+ * asked for them and none where it asked for none.
433
+ */ function fit(raw, planned) {
434
+ const blocks = isRecord(raw) && Array.isArray(raw['blocks']) ? raw['blocks'].filter(isRecord) : [];
435
+ const said = wordsOf(blocks.filter((block)=>block['kind'] === 'heading' || block['kind'] === 'eyebrow').map((block)=>{
436
+ var _block_text;
437
+ return String((_block_text = block['text']) != null ? _block_text : '');
438
+ }).join(' '));
439
+ const named = wordsOf(planned.name);
440
+ let shared = 0;
441
+ for (const word of named)if (said.has(word) || [
442
+ ...said
443
+ ].some((other)=>other.startsWith(word) || word.startsWith(other))) shared += 1;
444
+ const showsItems = blocks.some((block)=>ITEM_KINDS.has(String(block['kind'])) && (block['kind'] === 'component' || Array.isArray(block['items']) && block['items'].length > 0));
445
+ const wantsItems = planned.items > 0;
446
+ return shared * 2 + (wantsItems === showsItems ? 1.5 : -1.5);
447
+ }
448
+ /**
449
+ * The answer's sections that best match the plan, in order, where it gave
450
+ * more than the plan names (AGL-3660): a model that opened a page with a
451
+ * hero of its own, or closed it with a call to action, shifted every planned
452
+ * section by one, and the cut fell on the plan's last sections — the ones
453
+ * that carried its items. Each planned section takes the answer's section
454
+ * that fits it best, keeping the answer's order.
455
+ */ function alignSections(given, planned) {
456
+ const n = given.length;
457
+ const m = planned.length;
458
+ // best[i][j]: the best score placing plan sections j.. on answer sections i..
459
+ const best = Array.from({
460
+ length: n + 1
461
+ }, ()=>Array(m + 1).fill(-Infinity));
462
+ for(let i = 0; i <= n; i += 1)best[i][m] = 0;
463
+ for(let i = n - 1; i >= 0; i -= 1){
464
+ for(let j = m - 1; j >= 0; j -= 1){
465
+ if (n - i < m - j) continue;
466
+ best[i][j] = Math.max(best[i + 1][j], fit(given[i], planned[j]) + best[i + 1][j + 1]);
467
+ }
468
+ }
469
+ const picked = [];
470
+ let i = 0;
471
+ for(let j = 0; j < m; j += 1){
472
+ while(i < n && best[i][j] !== fit(given[i], planned[j]) + best[i + 1][j + 1])i += 1;
473
+ picked.push(i);
474
+ i += 1;
475
+ }
476
+ return picked;
477
+ }
478
+ /**
479
+ * The page an answer describes, against the sections its plan names: each
480
+ * planned section takes the answer's section at its position — or, where the
481
+ * answer gave more sections than the plan names and the plan's sections are
482
+ * given, the one that fits it best (`alignSections`) — an answer's extra
483
+ * sections are left out, and a planned section the answer left empty or out
484
+ * is `null` — the one thing a re-ask asks for again.
485
+ */ export function aiReadLayoutPage(raw, planned, plan) {
404
486
  const settled = [];
405
487
  const given = isRecord(raw) && Array.isArray(raw['sections']) ? raw['sections'] : [];
488
+ const aligned = given.length > planned && plan && plan.length === planned ? alignSections(given, plan) : Array.from({
489
+ length: planned
490
+ }, (_, index)=>index);
406
491
  if (given.length > planned) {
407
492
  settled.push({
408
493
  at: 'sections',
409
- what: `${given.length} sections for a plan of ${planned}; the extra left out`
494
+ what: `${given.length} sections for a plan of ${planned}; ${aligned.every((at, index)=>at === index) ? 'the extra left out' : `sections ${aligned.map((at)=>at + 1).join(', ')} kept as the plan's`}`
410
495
  });
411
496
  }
412
- const sections = Array.from({
413
- length: planned
414
- }, (_, index)=>aiReadLayoutSection(given[index], `sections[${index}]`, settled));
497
+ const sections = aligned.map((at, index)=>aiReadLayoutSection(given[at], `sections[${index}]`, settled));
415
498
  return {
416
499
  sections,
417
500
  settled
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-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 { AiTool } from '../providers/contract'\nimport { AI_ICON_WORDS } from '../runtime/ai-icon-library'\n\n/**\n * The compact layout language (AGL-3660): what a model designs a page, and a\n * site's header and footer, in — and the one place its shape is declared.\n *\n * ── Why a language, not a tree ───────────────────────────────────────────\n *\n * A page used to be asked for as a raw element tree, packed as escaped JSON\n * text inside a tool call and held to the doctrine's building rules: Grid\n * items sized as strings, every child attached, one h1, a destination on\n * every button, headings under their ceiling. A real model kept about half\n * of a page's sections on the first try, and a Free section was cut off at\n * its token ceiling. The founder chose a language over a library of section\n * templates (10/7): every page is designed for its own request, so no two\n * sites come out alike, and the rules hold because CODE writes the tree.\n *\n * So the model says WHAT each section is and how it is arranged, in a few\n * words of structure, and writes every word a visitor reads; the compiler\n * (`ai-layout-compiler.ts`) turns that into the element tree with the\n * palette's elements and the theme's tokens, by construction.\n *\n * ── The shape ────────────────────────────────────────────────────────────\n *\n * A page is its plan's sections, in order. A SECTION is\n * - `band` — its background: `plain` (the page), `soft` (a paper band),\n * `brand` (the brand color) or `dark` (always dark);\n * - `cols` — a row of columns by relative width, as `[7, 5]` or `[1, 1, 1]`,\n * up to four; absent, one column;\n * - `align` — `start` or `center`;\n * - `blocks` — what it shows, top to bottom.\n *\n * A BLOCK is one thing a visitor reads or uses. Its `kind` is one of\n * `AI_LAYOUT_BLOCK_KINDS`; `col` places it in a column of the row (0 first),\n * and a block with no `col` spans the section — above the row when it comes\n * before the first placed block, below it when it comes after. `text` is its\n * words, `to` where it goes, `style` its emphasis and `icon` a word from the\n * icon library. A group — `list`, `cards`, `steps`, `stats`, `quotes`,\n * `faq` — carries its `items`, each a `title`, a `text`, and optionally its\n * own `to` and `icon`.\n *\n * Where a block goes (`to`): `page:<id>` for a page of this site (its label\n * works too), `#<n>` for section n of this page, `form` for the site's form,\n * a form's or a component's id for a `form` or `component` block, or an\n * `https:` address the brief gave.\n *\n * A site's header and footer are two sections in the same language\n * (`AI_LAYOUT_FRAME_TOOL`): the header's brand and navigation are the\n * site's own and written by the compiler, so the model gives the header its\n * band, its alignment and an optional call-to-action button; the footer is\n * a section like any other.\n *\n * ── Cheap by shape ───────────────────────────────────────────────────────\n *\n * The schema is a structured object in the tool input (never JSON text in a\n * string), small enough for a strict tool (`tool-schema-limits.spec.ts`), and\n * the language's instructions ride in the cached prefix. A section is a few\n * dozen words of structure beside its copy, so a whole page is one answer.\n */\n\n/** Every kind of block, in the order the schema lists them. */\nexport const AI_LAYOUT_BLOCK_KINDS = [\n 'eyebrow',\n 'heading',\n 'lede',\n 'text',\n 'note',\n 'list',\n 'button',\n 'image',\n 'cards',\n 'steps',\n 'stats',\n 'quotes',\n 'faq',\n 'form',\n 'component',\n] as const\n\nexport type AiLayoutBlockKind = (typeof AI_LAYOUT_BLOCK_KINDS)[number]\n\n/** The kinds that carry `items`. */\nexport const AI_LAYOUT_GROUP_KINDS: ReadonlySet<AiLayoutBlockKind> = new Set([\n 'list',\n 'cards',\n 'steps',\n 'stats',\n 'quotes',\n 'faq',\n])\n\nexport const AI_LAYOUT_BANDS = ['plain', 'soft', 'brand', 'dark'] as const\nexport type AiLayoutBand = (typeof AI_LAYOUT_BANDS)[number]\n\nexport const AI_LAYOUT_ALIGNS = ['start', 'center'] as const\nexport type AiLayoutAlign = (typeof AI_LAYOUT_ALIGNS)[number]\n\nexport const AI_LAYOUT_STYLES = [\n 'primary',\n 'secondary',\n 'quiet',\n 'large',\n] as const\nexport type AiLayoutStyle = (typeof AI_LAYOUT_STYLES)[number]\n\n/** The most columns a row holds; a fifth is folded into the fourth. */\nexport const AI_LAYOUT_MAX_COLS = 4\n\n/**\n * The most items a group shows: one under the eight copies rule 8 reads as a\n * list typed out by hand, which belongs in a dataset.\n */\nexport const AI_LAYOUT_MAX_ITEMS = 7\n\n/** The most quotes a group shows: each is a gap the owner fills with a real one. */\nexport const AI_LAYOUT_MAX_QUOTES = 3\n\nexport interface AiLayoutItem {\n title: string\n text: string\n to?: string\n icon?: string\n}\n\nexport interface AiLayoutBlock {\n kind: AiLayoutBlockKind\n col?: number\n text?: string\n to?: string\n style?: AiLayoutStyle\n icon?: string\n items?: AiLayoutItem[]\n}\n\nexport interface AiLayoutSection {\n band?: AiLayoutBand\n cols?: number[]\n align?: AiLayoutAlign\n blocks: AiLayoutBlock[]\n}\n\n/** A page: its plan's sections, in order. */\nexport interface AiLayoutPageDoc {\n sections: AiLayoutSection[]\n}\n\n/** A site's frame: the header and the footer every page renders between. */\nexport interface AiLayoutFrameDoc {\n header: AiLayoutSection\n footer: AiLayoutSection\n}\n\nconst ITEM_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['title', 'text', 'to', 'icon'],\n properties: {\n title: { type: 'string' },\n text: { type: 'string' },\n to: { type: 'string' },\n icon: { type: 'string' },\n },\n} as const\n\nconst BLOCK_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'col', 'text', 'to', 'style', 'icon', 'items'],\n properties: {\n kind: { type: 'string', enum: [...AI_LAYOUT_BLOCK_KINDS] },\n col: { type: 'integer' },\n text: { type: 'string' },\n to: { type: 'string' },\n style: { type: 'string', enum: [...AI_LAYOUT_STYLES, 'none'] },\n icon: { type: 'string' },\n items: { type: 'array', items: ITEM_SCHEMA },\n },\n} as const\n\nconst SECTION_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['band', 'cols', 'align', 'blocks'],\n properties: {\n band: { type: 'string', enum: [...AI_LAYOUT_BANDS] },\n cols: { type: 'array', items: { type: 'integer' } },\n align: { type: 'string', enum: [...AI_LAYOUT_ALIGNS] },\n blocks: { type: 'array', items: BLOCK_SCHEMA },\n },\n} as const\n\n/** The page tool: every section of one page, in plan order, in one answer. */\nexport const AI_LAYOUT_PAGE_TOOL: AiTool = {\n name: 'submit_page',\n description:\n 'Submit the page: one entry in sections for each planned section, in order, in the layout language.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['sections'],\n properties: { sections: { type: 'array', items: SECTION_SCHEMA } },\n },\n}\n\n/** The frame tool: the site's header and footer, in the same language. */\nexport const AI_LAYOUT_FRAME_TOOL: AiTool = {\n name: 'submit_frame',\n description:\n \"Submit the site's header and footer, each as a section in the layout language.\",\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['header', 'footer'],\n properties: { header: SECTION_SCHEMA, footer: SECTION_SCHEMA },\n },\n}\n\n/**\n * The language as a model is taught it: the cached instructions both doors\n * share. Every word of copy is the model's; this says only how a design is\n * written down and what each word becomes.\n */\nexport const AI_LAYOUT_LANGUAGE_TEXT = [\n \"You design web pages in a compact layout language. You decide the structure and write every word; the platform turns your design into the page, with the site's theme, spacing and type, so never describe styling beyond the words below.\",\n '',\n 'A page is a list of sections, one for each section the plan names, in that order. A section has:',\n '- band: plain (the page background), soft (a light band), brand (the brand color) or dark (always dark). Vary the bands down a page so neighboring sections read apart; keep brand and dark to one or two sections a page.',\n '- cols: relative column widths for a row, such as [1, 1] for halves, [7, 5] for a wide and a narrow column, or [1, 1, 1]; at most 4. [] for one column.',\n '- align: start or center.',\n '- blocks: what the section shows, top to bottom. A block with col 0 or more goes in that column of the row (0 is the first); a block with col -1 spans the whole section, above the row when it comes first and below it when it comes last.',\n 'Every field is always written: a block that does not use one writes \"\" (text, to, icon), \"none\" (style), -1 (col) or [] (items); an item that does not use one writes \"\".',\n '',\n 'Block kinds:',\n \"- eyebrow: a short label above a heading. heading: a heading; the first heading of the page's first section is the page's title, and the first heading of every other section is that section's heading. lede: the larger sentence under a heading. text: a paragraph. note: small print.\",\n '- button: text is the label and to says where it goes; style primary, secondary or quiet. Two buttons in a row read as a pair.',\n '- image: text describes the picture to place, which becomes its alt text; the owner adds the picture. icon names a drawing to show until then.',\n '- list: items are the lines (title only; text may be empty).',\n \"- cards, steps, stats, faq: items, each with a title and a text. cards are features or services; steps are numbered; stats are a figure (title) with its label (text), only for figures the brief gives; faq items are a question (title) and its answer (text). Use no quotes block: the site has no customer's words to show.\",\n \"- form: places a saved form by its id in to. component: places a reusable component by its id in to; its items fill the component's props, title the prop name and text the value.\",\n '- style large on a heading makes the page title display-sized. icon (on a block or an item) is one of: ' +\n AI_ICON_WORDS.join(', ') +\n '.',\n '',\n \"Where a link goes (to): page:<page id> for a page of this site; #<n> for section n of this page (counted from 1); form for the site's form; or an https: address the brief gives. A button the page has nowhere to send is left out. A to value is never written into words: in text, name a page by its title.\",\n '',\n \"Writing: plain, specific copy in the site's voice, from the brief, the business and its audience, never filler. Keep a page title under 70 characters and a section heading under 80. The site is published exactly as you write it, so where the brief leaves out a fact such as a price, a figure, a name, a phone number, an email, an address or opening hours, leave it out: write the sentence without it, and make no list line, card or contact row for it. Never write a placeholder or a gap in square brackets, and never invent the fact. Never invent a customer, a review, an award or a statistic.\",\n '',\n 'Design each page for this business and this request: choose the bands, the rows and the blocks that tell its story best, so no two sites read alike.',\n].join('\\n')\n\n// ── Reading an answer ────────────────────────────────────────────────────\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined\n}\n\nfunction oneOf<T extends string>(\n value: unknown,\n values: readonly T[],\n): T | undefined {\n return typeof value === 'string' &&\n (values as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** What the reader settled on its own, for the job's record and the specs. */\nexport interface AiLayoutSettlement {\n /** Where: `sections[2].blocks[1]`, `header`. */\n at: string\n what: string\n}\n\nfunction readItem(raw: unknown): AiLayoutItem | null {\n if (!isRecord(raw)) return null\n const item: AiLayoutItem = {\n title: typeof raw['title'] === 'string' ? raw['title'] : '',\n text: typeof raw['text'] === 'string' ? raw['text'] : '',\n }\n if (!item.title.trim() && !item.text.trim()) return null\n const to = text(raw['to'])\n const icon = text(raw['icon'])\n return { ...item, ...(to ? { to } : {}), ...(icon ? { icon } : {}) }\n}\n\nfunction readBlock(\n raw: unknown,\n at: string,\n settled: AiLayoutSettlement[],\n): AiLayoutBlock | null {\n if (!isRecord(raw)) {\n settled.push({ at, what: 'not a block; left out' })\n return null\n }\n const kind = oneOf(raw['kind'], AI_LAYOUT_BLOCK_KINDS)\n if (!kind) {\n settled.push({\n at,\n what: `unknown kind ${JSON.stringify(raw['kind'] ?? null)}; left out`,\n })\n return null\n }\n const block: AiLayoutBlock = { kind }\n if (\n typeof raw['col'] === 'number' &&\n Number.isInteger(raw['col']) &&\n raw['col'] >= 0\n )\n block.col = raw['col']\n const words = text(raw['text'])\n if (words) block.text = words\n const to = text(raw['to'])\n if (to) block.to = to.trim()\n const style = oneOf(raw['style'], AI_LAYOUT_STYLES)\n if (style) block.style = style\n const icon = text(raw['icon'])\n if (icon) block.icon = icon.trim().toLowerCase()\n if (AI_LAYOUT_GROUP_KINDS.has(kind) || kind === 'component') {\n const items = (Array.isArray(raw['items']) ? raw['items'] : [])\n .map(readItem)\n .filter((item): item is AiLayoutItem => !!item)\n const most =\n kind === 'quotes'\n ? AI_LAYOUT_MAX_QUOTES\n : kind === 'component'\n ? Infinity\n : AI_LAYOUT_MAX_ITEMS\n if (items.length > most)\n settled.push({\n at,\n what: `${items.length} items; the first ${most} kept`,\n })\n if (items.length) block.items = items.slice(0, most)\n }\n // A block with nothing to show is no block: a group with no items, words\n // with no words. A form or a component may name its record alone.\n const empty = AI_LAYOUT_GROUP_KINDS.has(kind)\n ? !block.items?.length\n : kind === 'form' || kind === 'component'\n ? false\n : !block.text\n if (empty) {\n settled.push({ at, what: `an empty ${kind}; left out` })\n return null\n }\n return block\n}\n\n/** A section read from an answer, or `null` with why it could not be. */\nexport function aiReadLayoutSection(\n raw: unknown,\n at: string,\n settled: AiLayoutSettlement[],\n): AiLayoutSection | null {\n if (!isRecord(raw)) return null\n const section: AiLayoutSection = { blocks: [] }\n const band = oneOf(raw['band'], AI_LAYOUT_BANDS)\n if (band) section.band = band\n const align = oneOf(raw['align'], AI_LAYOUT_ALIGNS)\n if (align) section.align = align\n if (Array.isArray(raw['cols'])) {\n const cols = raw['cols'].filter(\n (value): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0,\n )\n if (cols.length > AI_LAYOUT_MAX_COLS) {\n settled.push({\n at: `${at}.cols`,\n what: `${cols.length} columns; the first ${AI_LAYOUT_MAX_COLS} kept`,\n })\n }\n if (cols.length >= 2)\n section.cols = cols\n .slice(0, AI_LAYOUT_MAX_COLS)\n .map((value) => Math.min(12, Math.max(1, Math.round(value))))\n }\n const blocks = Array.isArray(raw['blocks']) ? raw['blocks'] : []\n blocks.forEach((entry, index) => {\n const block = readBlock(entry, `${at}.blocks[${index}]`, settled)\n if (!block) return\n if (\n block.col !== undefined &&\n section.cols &&\n block.col >= section.cols.length\n ) {\n settled.push({\n at: `${at}.blocks[${index}]`,\n what: `column ${block.col} of ${section.cols.length}; placed in the last`,\n })\n block.col = section.cols.length - 1\n }\n if (block.col !== undefined && !section.cols) delete block.col\n section.blocks.push(block)\n })\n return section.blocks.length ? section : null\n}\n\n/** A page answer read: the sections it gave, by plan position, and the positions it could not. */\nexport interface AiLayoutPageReading {\n /** One entry per planned section; `null` where the answer gave nothing usable. */\n sections: Array<AiLayoutSection | null>\n settled: AiLayoutSettlement[]\n}\n\n/**\n * The page an answer describes, against the number of sections its plan\n * names: each planned section takes the answer's section at its position,\n * an answer's extra sections are left out, and a planned section the answer\n * left empty or out is `null` — the one thing a re-ask asks for again.\n */\nexport function aiReadLayoutPage(\n raw: unknown,\n planned: number,\n): AiLayoutPageReading {\n const settled: AiLayoutSettlement[] = []\n const given =\n isRecord(raw) && Array.isArray(raw['sections']) ? raw['sections'] : []\n if (given.length > planned) {\n settled.push({\n at: 'sections',\n what: `${given.length} sections for a plan of ${planned}; the extra left out`,\n })\n }\n const sections = Array.from({ length: planned }, (_, index) =>\n aiReadLayoutSection(given[index], `sections[${index}]`, settled),\n )\n return { sections, settled }\n}\n\n/** A frame answer read: a header and a footer, either `null` where it was not usable. */\nexport function aiReadLayoutFrame(raw: unknown): {\n header: AiLayoutSection | null\n footer: AiLayoutSection | null\n settled: AiLayoutSettlement[]\n} {\n const settled: AiLayoutSettlement[] = []\n const record = isRecord(raw) ? raw : {}\n // A header needs no block of its own: its brand and navigation are the\n // site's, so an empty one is a plain bar.\n const header = isRecord(record['header'])\n ? (aiReadLayoutSection(record['header'], 'header', settled) ??\n readBare(record['header']))\n : null\n const footer = aiReadLayoutSection(record['footer'], 'footer', settled)\n return { header, footer, settled }\n}\n\n/** A section with no usable block, keeping only its band and alignment. */\nfunction readBare(raw: Record<string, unknown>): AiLayoutSection {\n const band = oneOf(raw['band'], AI_LAYOUT_BANDS)\n const align = oneOf(raw['align'], AI_LAYOUT_ALIGNS)\n return { ...(band ? { band } : {}), ...(align ? { align } : {}), blocks: [] }\n}\n"],"names":["AI_ICON_WORDS","AI_LAYOUT_BLOCK_KINDS","AI_LAYOUT_GROUP_KINDS","Set","AI_LAYOUT_BANDS","AI_LAYOUT_ALIGNS","AI_LAYOUT_STYLES","AI_LAYOUT_MAX_COLS","AI_LAYOUT_MAX_ITEMS","AI_LAYOUT_MAX_QUOTES","ITEM_SCHEMA","type","additionalProperties","required","properties","title","text","to","icon","BLOCK_SCHEMA","kind","enum","col","style","items","SECTION_SCHEMA","band","cols","align","blocks","AI_LAYOUT_PAGE_TOOL","name","description","strict","inputSchema","sections","AI_LAYOUT_FRAME_TOOL","header","footer","AI_LAYOUT_LANGUAGE_TEXT","join","isRecord","value","Array","isArray","trim","undefined","oneOf","values","includes","readItem","raw","item","readBlock","at","settled","block","push","what","JSON","stringify","Number","isInteger","words","toLowerCase","has","map","filter","most","Infinity","length","slice","empty","aiReadLayoutSection","section","isFinite","Math","min","max","round","forEach","entry","index","aiReadLayoutPage","planned","given","from","_","aiReadLayoutFrame","record","readBare"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-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 { AiTool } from '../providers/contract'\nimport { AI_ICON_WORDS } from '../runtime/ai-icon-library'\n\n/**\n * The compact layout language (AGL-3660): what a model designs a page, and a\n * site's header and footer, in — and the one place its shape is declared.\n *\n * ── Why a language, not a tree ───────────────────────────────────────────\n *\n * A page used to be asked for as a raw element tree, packed as escaped JSON\n * text inside a tool call and held to the doctrine's building rules: Grid\n * items sized as strings, every child attached, one h1, a destination on\n * every button, headings under their ceiling. A real model kept about half\n * of a page's sections on the first try, and a Free section was cut off at\n * its token ceiling. The founder chose a language over a library of section\n * templates (10/7): every page is designed for its own request, so no two\n * sites come out alike, and the rules hold because CODE writes the tree.\n *\n * So the model says WHAT each section is and how it is arranged, in a few\n * words of structure, and writes every word a visitor reads; the compiler\n * (`ai-layout-compiler.ts`) turns that into the element tree with the\n * palette's elements and the theme's tokens, by construction.\n *\n * ── The shape ────────────────────────────────────────────────────────────\n *\n * A page is its plan's sections, in order. A SECTION is\n * - `band` — its background: `plain` (the page), `soft` (a paper band),\n * `brand` (the brand color) or `dark` (always dark);\n * - `cols` — a row of columns by relative width, as `[7, 5]` or `[1, 1, 1]`,\n * up to four; absent, one column;\n * - `align` — `start` or `center`;\n * - `blocks` — what it shows, top to bottom.\n *\n * A BLOCK is one thing a visitor reads or uses. Its `kind` is one of\n * `AI_LAYOUT_BLOCK_KINDS`; `col` places it in a column of the row (0 first),\n * and a block with no `col` spans the section — above the row when it comes\n * before the first placed block, below it when it comes after. `text` is its\n * words, `to` where it goes, `style` its emphasis and `icon` a word from the\n * icon library. A group — `list`, `cards`, `steps`, `stats`, `quotes`,\n * `faq` — carries its `items`, each a `title`, a `text`, and optionally its\n * own `to` and `icon`.\n *\n * Where a block goes (`to`): `page:<id>` for a page of this site (its label\n * works too), `#<n>` for section n of this page, `form` for the site's form,\n * a form's or a component's id for a `form` or `component` block, or an\n * `https:` address the brief gave.\n *\n * A site's header and footer are two sections in the same language\n * (`AI_LAYOUT_FRAME_TOOL`): the header's brand and navigation are the\n * site's own and written by the compiler, so the model gives the header its\n * band, its alignment and an optional call-to-action button; the footer is\n * a section like any other.\n *\n * ── Cheap by shape ───────────────────────────────────────────────────────\n *\n * The schema is a structured object in the tool input (never JSON text in a\n * string), small enough for a strict tool (`tool-schema-limits.spec.ts`), and\n * the language's instructions ride in the cached prefix. A section is a few\n * dozen words of structure beside its copy, so a whole page is one answer.\n */\n\n/** Every kind of block, in the order the schema lists them. */\nexport const AI_LAYOUT_BLOCK_KINDS = [\n 'eyebrow',\n 'heading',\n 'lede',\n 'text',\n 'note',\n 'list',\n 'button',\n 'image',\n 'cards',\n 'steps',\n 'stats',\n 'quotes',\n 'faq',\n 'form',\n 'component',\n] as const\n\nexport type AiLayoutBlockKind = (typeof AI_LAYOUT_BLOCK_KINDS)[number]\n\n/** The kinds that carry `items`. */\nexport const AI_LAYOUT_GROUP_KINDS: ReadonlySet<AiLayoutBlockKind> = new Set([\n 'list',\n 'cards',\n 'steps',\n 'stats',\n 'quotes',\n 'faq',\n])\n\nexport const AI_LAYOUT_BANDS = ['plain', 'soft', 'brand', 'dark'] as const\nexport type AiLayoutBand = (typeof AI_LAYOUT_BANDS)[number]\n\nexport const AI_LAYOUT_ALIGNS = ['start', 'center'] as const\nexport type AiLayoutAlign = (typeof AI_LAYOUT_ALIGNS)[number]\n\nexport const AI_LAYOUT_STYLES = [\n 'primary',\n 'secondary',\n 'quiet',\n 'large',\n] as const\nexport type AiLayoutStyle = (typeof AI_LAYOUT_STYLES)[number]\n\n/** The most columns a row holds; a fifth is folded into the fourth. */\nexport const AI_LAYOUT_MAX_COLS = 4\n\n/**\n * The most items a group shows: one under the eight copies rule 8 reads as a\n * list typed out by hand, which belongs in a dataset.\n */\nexport const AI_LAYOUT_MAX_ITEMS = 7\n\n/** The most quotes a group shows: each is a gap the owner fills with a real one. */\nexport const AI_LAYOUT_MAX_QUOTES = 3\n\nexport interface AiLayoutItem {\n title: string\n text: string\n to?: string\n icon?: string\n}\n\nexport interface AiLayoutBlock {\n kind: AiLayoutBlockKind\n col?: number\n text?: string\n to?: string\n style?: AiLayoutStyle\n icon?: string\n items?: AiLayoutItem[]\n}\n\nexport interface AiLayoutSection {\n band?: AiLayoutBand\n cols?: number[]\n align?: AiLayoutAlign\n blocks: AiLayoutBlock[]\n}\n\n/** A page: its plan's sections, in order. */\nexport interface AiLayoutPageDoc {\n sections: AiLayoutSection[]\n}\n\n/** A site's frame: the header and the footer every page renders between. */\nexport interface AiLayoutFrameDoc {\n header: AiLayoutSection\n footer: AiLayoutSection\n}\n\nconst ITEM_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['title', 'text', 'to', 'icon'],\n properties: {\n title: { type: 'string' },\n text: { type: 'string' },\n to: { type: 'string' },\n icon: { type: 'string' },\n },\n} as const\n\nconst BLOCK_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'col', 'text', 'to', 'style', 'icon', 'items'],\n properties: {\n kind: { type: 'string', enum: [...AI_LAYOUT_BLOCK_KINDS] },\n col: { type: 'integer' },\n text: { type: 'string' },\n to: { type: 'string' },\n style: { type: 'string', enum: [...AI_LAYOUT_STYLES, 'none'] },\n icon: { type: 'string' },\n items: { type: 'array', items: ITEM_SCHEMA },\n },\n} as const\n\nconst SECTION_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['band', 'cols', 'align', 'blocks'],\n properties: {\n band: { type: 'string', enum: [...AI_LAYOUT_BANDS] },\n cols: { type: 'array', items: { type: 'integer' } },\n align: { type: 'string', enum: [...AI_LAYOUT_ALIGNS] },\n blocks: { type: 'array', items: BLOCK_SCHEMA },\n },\n} as const\n\n/** The page tool: every section of one page, in plan order, in one answer. */\nexport const AI_LAYOUT_PAGE_TOOL: AiTool = {\n name: 'submit_page',\n description:\n 'Submit the page: one entry in sections for each planned section, in order, in the layout language.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['sections'],\n properties: { sections: { type: 'array', items: SECTION_SCHEMA } },\n },\n}\n\n/** The frame tool: the site's header and footer, in the same language. */\nexport const AI_LAYOUT_FRAME_TOOL: AiTool = {\n name: 'submit_frame',\n description:\n \"Submit the site's header and footer, each as a section in the layout language.\",\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['header', 'footer'],\n properties: { header: SECTION_SCHEMA, footer: SECTION_SCHEMA },\n },\n}\n\n/**\n * The language as a model is taught it: the cached instructions both doors\n * share. Every word of copy is the model's; this says only how a design is\n * written down and what each word becomes.\n */\nexport const AI_LAYOUT_LANGUAGE_TEXT = [\n \"You design web pages in a compact layout language. You decide the structure and write every word; the platform turns your design into the page, with the site's theme, spacing and type, so never describe styling beyond the words below.\",\n '',\n 'A page is a list of sections, one for each section the plan names, in that order. A section has:',\n '- band: plain (the page background), soft (a light band), brand (the brand color) or dark (always dark). Vary the bands down a page so neighboring sections read apart; keep brand and dark to one or two sections a page.',\n '- cols: relative column widths for a row, such as [1, 1] for halves, [7, 5] for a wide and a narrow column, or [1, 1, 1]; at most 4. [] for one column.',\n '- align: start or center.',\n '- blocks: what the section shows, top to bottom. A block with col 0 or more goes in that column of the row (0 is the first); a block with col -1 spans the whole section, above the row when it comes first and below it when it comes last.',\n 'Every field is always written: a block that does not use one writes \"\" (text, to, icon), \"none\" (style), -1 (col) or [] (items); an item that does not use one writes \"\".',\n '',\n 'Block kinds:',\n \"- eyebrow: a short label above a heading. heading: a heading; the first heading of the page's first section is the page's title, and the first heading of every other section is that section's heading. lede: the larger sentence under a heading. text: a paragraph. note: small print.\",\n '- button: text is the label and to says where it goes; style primary, secondary or quiet. Two buttons in a row read as a pair.',\n '- image: text describes the picture to place, which becomes its alt text; the owner adds the picture. icon names a drawing to show until then.',\n '- list: items are the lines (title only; text may be empty).',\n \"- cards, steps, stats, faq: items, each with a title and a text. cards are features or services; steps are numbered; stats are a figure (title) with its label (text), only for figures the brief gives; faq items are a question (title) and its answer (text). Use no quotes block: the site has no customer's words to show.\",\n \"- form: places a saved form by its id in to. component: places a reusable component by its id in to; its items fill the component's props, title the prop name and text the value.\",\n '- style large on a heading makes the page title display-sized. icon (on a block or an item) is one of: ' +\n AI_ICON_WORDS.join(', ') +\n '.',\n '',\n \"Where a link goes (to): page:<page id> for a page of this site; #<n> for section n of this page (counted from 1); form for the site's form; or an https: address the brief gives. A button the page has nowhere to send is left out. A to value is never written into words: in text, name a page by its title.\",\n '',\n \"Writing: plain, specific copy in the site's voice, from the brief, the business and its audience, never filler. Keep a page title under 70 characters and a section heading under 80. The site is published exactly as you write it, so where the brief leaves out a fact such as a price, a figure, a name, a phone number, an email, an address or opening hours, leave it out: write the sentence without it, and make no list line, card or contact row for it. Never write a placeholder or a gap in square brackets, and never invent the fact. Never invent a customer, a review, an award or a statistic.\",\n '',\n 'Design each page for this business and this request: choose the bands, the rows and the blocks that tell its story best, so no two sites read alike.',\n].join('\\n')\n\n// ── Reading an answer ────────────────────────────────────────────────────\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined\n}\n\nfunction oneOf<T extends string>(\n value: unknown,\n values: readonly T[],\n): T | undefined {\n return typeof value === 'string' &&\n (values as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** What the reader settled on its own, for the job's record and the specs. */\nexport interface AiLayoutSettlement {\n /** Where: `sections[2].blocks[1]`, `header`. */\n at: string\n what: string\n}\n\nfunction readItem(raw: unknown): AiLayoutItem | null {\n if (!isRecord(raw)) return null\n const item: AiLayoutItem = {\n title: typeof raw['title'] === 'string' ? raw['title'] : '',\n text: typeof raw['text'] === 'string' ? raw['text'] : '',\n }\n if (!item.title.trim() && !item.text.trim()) return null\n const to = text(raw['to'])\n const icon = text(raw['icon'])\n return { ...item, ...(to ? { to } : {}), ...(icon ? { icon } : {}) }\n}\n\nfunction readBlock(\n raw: unknown,\n at: string,\n settled: AiLayoutSettlement[],\n): AiLayoutBlock | null {\n if (!isRecord(raw)) {\n settled.push({ at, what: 'not a block; left out' })\n return null\n }\n const kind = oneOf(raw['kind'], AI_LAYOUT_BLOCK_KINDS)\n if (!kind) {\n settled.push({\n at,\n what: `unknown kind ${JSON.stringify(raw['kind'] ?? null)}; left out`,\n })\n return null\n }\n const block: AiLayoutBlock = { kind }\n if (\n typeof raw['col'] === 'number' &&\n Number.isInteger(raw['col']) &&\n raw['col'] >= 0\n )\n block.col = raw['col']\n const words = text(raw['text'])\n if (words) block.text = words\n const to = text(raw['to'])\n if (to) block.to = to.trim()\n const style = oneOf(raw['style'], AI_LAYOUT_STYLES)\n if (style) block.style = style\n const icon = text(raw['icon'])\n if (icon) block.icon = icon.trim().toLowerCase()\n if (AI_LAYOUT_GROUP_KINDS.has(kind) || kind === 'component') {\n const items = (Array.isArray(raw['items']) ? raw['items'] : [])\n .map(readItem)\n .filter((item): item is AiLayoutItem => !!item)\n const most =\n kind === 'quotes'\n ? AI_LAYOUT_MAX_QUOTES\n : kind === 'component'\n ? Infinity\n : AI_LAYOUT_MAX_ITEMS\n if (items.length > most)\n settled.push({\n at,\n what: `${items.length} items; the first ${most} kept`,\n })\n if (items.length) block.items = items.slice(0, most)\n }\n // A block with nothing to show is no block: a group with no items, words\n // with no words. A form or a component may name its record alone.\n const empty = AI_LAYOUT_GROUP_KINDS.has(kind)\n ? !block.items?.length\n : kind === 'form' || kind === 'component'\n ? false\n : !block.text\n if (empty) {\n settled.push({ at, what: `an empty ${kind}; left out` })\n return null\n }\n return block\n}\n\n/** A section read from an answer, or `null` with why it could not be. */\nexport function aiReadLayoutSection(\n raw: unknown,\n at: string,\n settled: AiLayoutSettlement[],\n): AiLayoutSection | null {\n if (!isRecord(raw)) return null\n const section: AiLayoutSection = { blocks: [] }\n const band = oneOf(raw['band'], AI_LAYOUT_BANDS)\n if (band) section.band = band\n const align = oneOf(raw['align'], AI_LAYOUT_ALIGNS)\n if (align) section.align = align\n if (Array.isArray(raw['cols'])) {\n const cols = raw['cols'].filter(\n (value): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0,\n )\n if (cols.length > AI_LAYOUT_MAX_COLS) {\n settled.push({\n at: `${at}.cols`,\n what: `${cols.length} columns; the first ${AI_LAYOUT_MAX_COLS} kept`,\n })\n }\n if (cols.length >= 2)\n section.cols = cols\n .slice(0, AI_LAYOUT_MAX_COLS)\n .map((value) => Math.min(12, Math.max(1, Math.round(value))))\n }\n const blocks = Array.isArray(raw['blocks']) ? raw['blocks'] : []\n blocks.forEach((entry, index) => {\n const block = readBlock(entry, `${at}.blocks[${index}]`, settled)\n if (!block) return\n if (\n block.col !== undefined &&\n section.cols &&\n block.col >= section.cols.length\n ) {\n settled.push({\n at: `${at}.blocks[${index}]`,\n what: `column ${block.col} of ${section.cols.length}; placed in the last`,\n })\n block.col = section.cols.length - 1\n }\n if (block.col !== undefined && !section.cols) delete block.col\n section.blocks.push(block)\n })\n return section.blocks.length ? section : null\n}\n\n/** A page answer read: the sections it gave, by plan position, and the positions it could not. */\nexport interface AiLayoutPageReading {\n /** One entry per planned section; `null` where the answer gave nothing usable. */\n sections: Array<AiLayoutSection | null>\n settled: AiLayoutSettlement[]\n}\n\n/** What a planned section is, as the reader matches an answer's sections to it. */\nexport interface AiLayoutPlannedSection {\n name: string\n items: number\n}\n\nconst WORD = /[a-z0-9]+/g\nconst STOP = new Set(['a', 'an', 'and', 'the', 'of', 'for', 'to', 'our', 'your', 'with', 'in', 'on', 'we', 'us', 'you'])\nconst wordsOf = (text: string) => new Set((text.toLowerCase().match(WORD) ?? []).filter((word) => !STOP.has(word)))\n\n/** The kinds that show a section's repeated items. */\nconst ITEM_KINDS = new Set(['list', 'cards', 'steps', 'stats', 'quotes', 'faq', 'component'])\n\n/**\n * How well an answer's section fits a planned one: the words its headings\n * share with the plan's name, and whether it shows items where the plan\n * asked for them and none where it asked for none.\n */\nfunction fit(raw: unknown, planned: AiLayoutPlannedSection): number {\n const blocks = isRecord(raw) && Array.isArray(raw['blocks']) ? raw['blocks'].filter(isRecord) : []\n const said = wordsOf(\n blocks\n .filter((block) => block['kind'] === 'heading' || block['kind'] === 'eyebrow')\n .map((block) => String(block['text'] ?? ''))\n .join(' '),\n )\n const named = wordsOf(planned.name)\n let shared = 0\n for (const word of named) if (said.has(word) || [...said].some((other) => other.startsWith(word) || word.startsWith(other))) shared += 1\n const showsItems = blocks.some((block) => ITEM_KINDS.has(String(block['kind'])) && (block['kind'] === 'component' || (Array.isArray(block['items']) && block['items'].length > 0)))\n const wantsItems = planned.items > 0\n return shared * 2 + (wantsItems === showsItems ? 1.5 : -1.5)\n}\n\n/**\n * The answer's sections that best match the plan, in order, where it gave\n * more than the plan names (AGL-3660): a model that opened a page with a\n * hero of its own, or closed it with a call to action, shifted every planned\n * section by one, and the cut fell on the plan's last sections — the ones\n * that carried its items. Each planned section takes the answer's section\n * that fits it best, keeping the answer's order.\n */\nfunction alignSections(given: readonly unknown[], planned: readonly AiLayoutPlannedSection[]): number[] {\n const n = given.length\n const m = planned.length\n // best[i][j]: the best score placing plan sections j.. on answer sections i..\n const best: number[][] = Array.from({ length: n + 1 }, () => Array(m + 1).fill(-Infinity))\n for (let i = 0; i <= n; i += 1) best[i][m] = 0\n for (let i = n - 1; i >= 0; i -= 1) {\n for (let j = m - 1; j >= 0; j -= 1) {\n if (n - i < m - j) continue\n best[i][j] = Math.max(best[i + 1][j], fit(given[i], planned[j]) + best[i + 1][j + 1])\n }\n }\n const picked: number[] = []\n let i = 0\n for (let j = 0; j < m; j += 1) {\n while (i < n && best[i][j] !== fit(given[i], planned[j]) + best[i + 1][j + 1]) i += 1\n picked.push(i)\n i += 1\n }\n return picked\n}\n\n/**\n * The page an answer describes, against the sections its plan names: each\n * planned section takes the answer's section at its position — or, where the\n * answer gave more sections than the plan names and the plan's sections are\n * given, the one that fits it best (`alignSections`) — an answer's extra\n * sections are left out, and a planned section the answer left empty or out\n * is `null` — the one thing a re-ask asks for again.\n */\nexport function aiReadLayoutPage(\n raw: unknown,\n planned: number,\n plan?: readonly AiLayoutPlannedSection[],\n): AiLayoutPageReading {\n const settled: AiLayoutSettlement[] = []\n const given =\n isRecord(raw) && Array.isArray(raw['sections']) ? raw['sections'] : []\n const aligned =\n given.length > planned && plan && plan.length === planned\n ? alignSections(given, plan)\n : Array.from({ length: planned }, (_, index) => index)\n if (given.length > planned) {\n settled.push({\n at: 'sections',\n what: `${given.length} sections for a plan of ${planned}; ${\n aligned.every((at, index) => at === index) ? 'the extra left out' : `sections ${aligned.map((at) => at + 1).join(', ')} kept as the plan's`\n }`,\n })\n }\n const sections = aligned.map((at, index) =>\n aiReadLayoutSection(given[at], `sections[${index}]`, settled),\n )\n return { sections, settled }\n}\n\n/** A frame answer read: a header and a footer, either `null` where it was not usable. */\nexport function aiReadLayoutFrame(raw: unknown): {\n header: AiLayoutSection | null\n footer: AiLayoutSection | null\n settled: AiLayoutSettlement[]\n} {\n const settled: AiLayoutSettlement[] = []\n const record = isRecord(raw) ? raw : {}\n // A header needs no block of its own: its brand and navigation are the\n // site's, so an empty one is a plain bar.\n const header = isRecord(record['header'])\n ? (aiReadLayoutSection(record['header'], 'header', settled) ??\n readBare(record['header']))\n : null\n const footer = aiReadLayoutSection(record['footer'], 'footer', settled)\n return { header, footer, settled }\n}\n\n/** A section with no usable block, keeping only its band and alignment. */\nfunction readBare(raw: Record<string, unknown>): AiLayoutSection {\n const band = oneOf(raw['band'], AI_LAYOUT_BANDS)\n const align = oneOf(raw['align'], AI_LAYOUT_ALIGNS)\n return { ...(band ? { band } : {}), ...(align ? { align } : {}), blocks: [] }\n}\n"],"names":["AI_ICON_WORDS","AI_LAYOUT_BLOCK_KINDS","AI_LAYOUT_GROUP_KINDS","Set","AI_LAYOUT_BANDS","AI_LAYOUT_ALIGNS","AI_LAYOUT_STYLES","AI_LAYOUT_MAX_COLS","AI_LAYOUT_MAX_ITEMS","AI_LAYOUT_MAX_QUOTES","ITEM_SCHEMA","type","additionalProperties","required","properties","title","text","to","icon","BLOCK_SCHEMA","kind","enum","col","style","items","SECTION_SCHEMA","band","cols","align","blocks","AI_LAYOUT_PAGE_TOOL","name","description","strict","inputSchema","sections","AI_LAYOUT_FRAME_TOOL","header","footer","AI_LAYOUT_LANGUAGE_TEXT","join","isRecord","value","Array","isArray","trim","undefined","oneOf","values","includes","readItem","raw","item","readBlock","at","settled","block","push","what","JSON","stringify","Number","isInteger","words","toLowerCase","has","map","filter","most","Infinity","length","slice","empty","aiReadLayoutSection","section","isFinite","Math","min","max","round","forEach","entry","index","WORD","STOP","wordsOf","match","word","ITEM_KINDS","fit","planned","said","String","named","shared","some","other","startsWith","showsItems","wantsItems","alignSections","given","n","m","best","from","fill","i","j","picked","aiReadLayoutPage","plan","aligned","_","every","aiReadLayoutFrame","record","readBare"],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { NodesMap } from '@aglyn/aglyn/types/nodes';
18
+ /**
19
+ * THE PICTURES OF A LANGUAGE PAGE (AGL-3660).
20
+ *
21
+ * The compiler writes a picture slot as a frame holding an icon and an
22
+ * `image` with alt text and no source (`ai-layout-compiler.ts`), and stays
23
+ * pure: it never looks a photo up. This module is what runs after it, on the
24
+ * stored page — it finds each empty slot, decides what kind of picture the
25
+ * slot is, and fills it with a photo the site can serve from its own origin.
26
+ *
27
+ * Two sources fill a slot, in order:
28
+ *
29
+ * 1. A stock photo searched for by the slot's own description and copied into
30
+ * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the
31
+ * deployment has a stock-photo key.
32
+ * 2. Otherwise, or when the search finds nothing fitting, one of the STARTER
33
+ * PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`, served at
34
+ * `/_static/starter/` by the tenant and the console alike, credited in the
35
+ * CREDITS file beside them). They are the platform's own files, so a
36
+ * visitor's browser asks no other host for them.
37
+ *
38
+ * The slot keeps the alt text the model wrote whichever source fills it.
39
+ */
40
+ /** What a picture slot is for, read from where it sits and what it says. */
41
+ export type AiLayoutPictureRole = 'hero' | 'about' | 'gallery';
42
+ /** One empty picture slot of a stored page, in document order. */
43
+ export interface AiLayoutPictureSlot {
44
+ /** The `image` node. */
45
+ imageId: string;
46
+ /** The frame around it, which sets the slot's shape. */
47
+ frameId: string | null;
48
+ /** The placeholder icon sharing the frame, taken out once a photo fills it. */
49
+ iconId: string | null;
50
+ alt: string;
51
+ /** The frame's shape as width over height; 1 when it names none. */
52
+ aspect: number;
53
+ /** The plan index of the section it sits in, or -1. */
54
+ sectionIndex: number;
55
+ role: AiLayoutPictureRole;
56
+ }
57
+ /** A photo a slot is filled with. */
58
+ export interface AiLayoutPicturePhoto {
59
+ /** The site-served address: a starter path or the media library's own. */
60
+ src: string;
61
+ width: number;
62
+ height: number;
63
+ }
64
+ /** A slot's role: the first section's picture is the hero; people make an about picture. */
65
+ export declare function aiLayoutPictureRole(input: {
66
+ sectionIndex: number;
67
+ sectionName: string;
68
+ alt: string;
69
+ }): AiLayoutPictureRole;
70
+ /**
71
+ * Every empty picture slot of a stored page, walked from its root in
72
+ * document order. A slot that already has a source — one the owner placed, or
73
+ * an earlier pass filled — is not a slot.
74
+ */
75
+ export declare function aiLayoutPictureSlots(nodes: NodesMap, rootId: string, sections: {
76
+ ids: readonly string[];
77
+ names: readonly string[];
78
+ }): AiLayoutPictureSlot[];
79
+ /** The starter photos, by name. */
80
+ export declare const AI_LAYOUT_STARTER_PHOTOS: {
81
+ readonly hero: {
82
+ readonly src: "/_static/starter/hero-team.jpg";
83
+ readonly width: 1600;
84
+ readonly height: 1067;
85
+ };
86
+ readonly about: {
87
+ readonly src: "/_static/starter/about-owner.jpg";
88
+ readonly width: 1600;
89
+ readonly height: 900;
90
+ };
91
+ readonly desk: {
92
+ readonly src: "/_static/starter/gallery-desk.jpg";
93
+ readonly width: 1600;
94
+ readonly height: 1068;
95
+ };
96
+ readonly plants: {
97
+ readonly src: "/_static/starter/gallery-plants.jpg";
98
+ readonly width: 1200;
99
+ readonly height: 900;
100
+ };
101
+ readonly craft: {
102
+ readonly src: "/_static/starter/gallery-craft.jpg";
103
+ readonly width: 1600;
104
+ readonly height: 1065;
105
+ };
106
+ };
107
+ /** A small stable number from a seed, so the same job always picks the same photos. */
108
+ export declare function aiLayoutSeedNumber(seed: string): number;
109
+ /**
110
+ * A starter photo for each slot. The hero's and the gallery's pools are
111
+ * rotated by the seed, so sites do not all open with the same photo, while an
112
+ * about picture always leads with the owner; no photo repeats on a page
113
+ * while one is still unused; and a page with more slots than photos starts
114
+ * the round again from its role's pool.
115
+ */
116
+ export declare function aiLayoutStarterPhotos(slots: readonly AiLayoutPictureSlot[], seed: string): AiLayoutPicturePhoto[];
117
+ /**
118
+ * The page with each slot filled by its photo: the `image` given its source,
119
+ * its intrinsic size and how it loads (the hero eagerly, as the starter home
120
+ * page loads its own), and the placeholder icon taken out of the frame. The
121
+ * alt text is kept as the model wrote it. A slot given no photo is left as
122
+ * the compiler wrote it.
123
+ */
124
+ export declare function aiLayoutPlacePictures(nodes: NodesMap, slots: readonly AiLayoutPictureSlot[], photos: ReadonlyArray<AiLayoutPicturePhoto | null>): NodesMap;
125
+ /**
126
+ * A source of photos found for the slots themselves — a stock search copied
127
+ * into the site's library — answering one photo or `null` per slot, in order.
128
+ * It may throw; a slot it leaves unfilled takes a starter photo.
129
+ */
130
+ export type AiLayoutPictureSource = (slots: readonly AiLayoutPictureSlot[]) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>;
131
+ /**
132
+ * Fills a stored page's empty picture slots (AGL-3660): from `source` where
133
+ * one is given and it answers, else from the starter photos. Never throws,
134
+ * and never leaves a page worse than it came: a picture is decoration, and a
135
+ * job never fails over one.
136
+ */
137
+ export declare function aiResolveLayoutPictures(nodes: NodesMap, input: {
138
+ rootId: string;
139
+ sectionIds: readonly string[];
140
+ sectionNames: readonly string[];
141
+ /** A per-job word, so two sites do not open with the same photo. */
142
+ seed: string;
143
+ source?: AiLayoutPictureSource | null;
144
+ }): Promise<NodesMap>;